chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/>
/// for executing multiple agents on the same task in parallel.
/// </summary>
public class Step01_Concurrent(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task ConcurrentTaskAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent physicist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in physics. You answer questions from a physics perspective.",
name: "Physicist",
description: "An expert in physics");
ChatCompletionAgent chemist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
name: "Chemist",
description: "An expert in chemistry");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
ConcurrentOrchestration orchestration =
new(physicist, chemist)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
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);
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
}
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Resources;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/> with structured output.
/// </summary>
public class Step01a_ConcurrentWithStructuredOutput(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
[Fact]
public async Task ConcurrentStructuredOutputAsync()
{
// Define the agents
ChatCompletionAgent agent1 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in identifying themes in articles. Given an article, identify the main themes.",
description: "An expert in identifying themes in articles");
ChatCompletionAgent agent2 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in sentiment analysis. Given an article, identify the sentiment.",
description: "An expert in sentiment analysis");
ChatCompletionAgent agent3 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in entity recognition. Given an article, extract the entities.",
description: "An expert in entity recognition");
// Define the orchestration with transform
Kernel kernel = this.CreateKernelWithChatCompletion();
StructuredOutputTransform<Analysis> outputTransform =
new(kernel.GetRequiredService<IChatCompletionService>(),
new OpenAIPromptExecutionSettings { ResponseFormat = typeof(Analysis) });
ConcurrentOrchestration<string, Analysis> orchestration =
new(agent1, agent2, agent3)
{
LoggerFactory = this.LoggerFactory,
ResultTransform = outputTransform.TransformAsync,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
const string resourceId = "Hamlet_full_play_summary.txt";
string input = EmbeddedResource.Read(resourceId);
Console.WriteLine($"\n# INPUT: @{resourceId}\n");
OrchestrationResult<Analysis> result = await orchestration.InvokeAsync(input, runtime);
Analysis output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
Console.WriteLine($"\n# RESULT:\n{JsonSerializer.Serialize(output, s_options)}");
await runtime.RunUntilIdleAsync();
}
private sealed class Analysis
{
public IList<string> Themes { get; set; } = [];
public IList<string> Sentiments { get; set; } = [];
public IList<string> Entities { get; set; } = [];
}
}
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="SequentialOrchestration"/> for
/// executing multiple agents in sequence, i.e.the output of one agent is
/// the input to the next agent.
/// </summary>
public class Step02_Sequential(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SequentialTaskAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent analystAgent =
this.CreateChatCompletionAgent(
name: "Analyst",
instructions:
"""
You are a marketing analyst. Given a product description, identify:
- Key features
- Target audience
- Unique selling points
""",
description: "A agent that extracts key concepts from a product description.");
ChatCompletionAgent writerAgent =
this.CreateChatCompletionAgent(
name: "copywriter",
instructions:
"""
You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
compose a compelling marketing copy (like a newsletter section) that highlights these points.
Output should be short (around 150 words), output just the copy as a single text block.
""",
description: "An agent that writes a marketing copy based on the extracted concepts.");
ChatCompletionAgent editorAgent =
this.CreateChatCompletionAgent(
name: "editor",
instructions:
"""
You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
give format and make it polished. Output the final improved copy as a single text block.
""",
description: "An agent that formats and proofreads the marketing copy.");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
SequentialOrchestration orchestration =
new(analystAgent, writerAgent, editorAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
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();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
}
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use cancel a <see cref="SequentialOrchestration"/> while its running.
/// </summary>
public class Step02a_SequentialCancellation(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task SequentialCancelledAsync()
{
// Define the agents
ChatCompletionAgent agent =
this.CreateChatCompletionAgent(
"""
If the input message is a number, return the number incremented by one.
""",
description: "A agent that increments numbers.");
// Define the orchestration
SequentialOrchestration orchestration = new(agent) { LoggerFactory = this.LoggerFactory };
// Start the runtime
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);
result.Cancel();
await Task.Delay(TimeSpan.FromSeconds(3));
try
{
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
}
catch
{
Console.WriteLine("\n# CANCELLED");
}
await runtime.RunUntilIdleAsync();
}
}
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/> ith a default
/// round robin manager for controlling the flow of conversation in a round robin fashion.
/// </summary>
/// <remarks>
/// Think of the group chat manager as a state machine, with the following possible states:
/// - Request for user message
/// - Termination, after which the manager will try to filter a result from the conversation
/// - Continuation, at which the manager will select the next agent to speak.
/// </remarks>
public class Step03_GroupChat(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task GroupChatAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
ChatCompletionAgent editor =
this.CreateChatCompletionAgent(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
GroupChatOrchestration orchestration =
new(new AuthorCriticManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input = "Create a slogan for a new electric 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();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class AuthorCriticManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The reviewer has approved the copy." };
}
}
return result;
}
}
}
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/> with human in the loop
/// </summary>
public class Step03a_GroupChatWithHumanInTheLoop(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task GroupChatWithHumanAsync()
{
// Define the agents
ChatCompletionAgent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
ChatCompletionAgent editor =
this.CreateChatCompletionAgent(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
bool didUserRespond = false;
GroupChatOrchestration orchestration =
new(
new HumanInTheLoopChatManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5,
InteractiveCallback = () =>
{
// Simlulate user input that first replies "No" and then "Yes"
ChatMessageContent input = new(AuthorRole.User, didUserRespond ? "Yes" : "More pizzazz");
didUserRespond = true;
Console.WriteLine($"\n# INPUT: {input.Content}\n");
return ValueTask.FromResult(input);
}
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "Create a slogan for a new electric 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();
}
/// <summary>
/// Define a custom group chat manager that enables user input.
/// </summary>
/// <remarks>
/// User input is achieved by overriding the default round robin manager
/// to allow user input after the reviewer agent's message.
/// </remarks>
private sealed class HumanInTheLoopChatManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.Role == AuthorRole.User && $"{lastMessage}".Contains("Yes", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The user is satisfied with the copy." };
}
}
return result;
}
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is null)
{
return ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "No agents have spoken yet." });
}
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
return ValueTask.FromResult(new GroupChatManagerResult<bool>(true) { Reason = "User input is needed after the reviewer's message." });
}
return ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "User input is not needed until the reviewer's message." });
}
}
}
@@ -0,0 +1,214 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/>
/// with a group chat manager that uses a chat completion service to
/// control the flow of the conversation.
/// </summary>
public class Step03b_GroupChatWithAIManager(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task GroupChatWithAIManagerAsync()
{
// Define the agents
ChatCompletionAgent farmer =
this.CreateChatCompletionAgent(
name: "Farmer",
description: "A rural farmer from Southeast Asia.",
instructions:
"""
You're a farmer from Southeast Asia.
Your life is deeply connected to land and family.
You value tradition and sustainability.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent developer =
this.CreateChatCompletionAgent(
name: "Developer",
description: "An urban software developer from the United States.",
instructions:
"""
You're a software developer from the United States.
Your life is fast-paced and technology-driven.
You value innovation, freedom, and work-life balance.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent teacher =
this.CreateChatCompletionAgent(
name: "Teacher",
description: "A retired history teacher from Eastern Europe",
instructions:
"""
You're a retired history teacher from Eastern Europe.
You bring historical and philosophical perspectives to discussions.
You value legacy, learning, and cultural continuity.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent activist =
this.CreateChatCompletionAgent(
name: "Activist",
description: "A young activist from South America.",
instructions:
"""
You're a young activist from South America.
You focus on social justice, environmental rights, and generational change.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent spiritual =
this.CreateChatCompletionAgent(
name: "SpiritualLeader",
description: "A spiritual leader from the Middle East.",
instructions:
"""
You're a spiritual leader from the Middle East.
You provide insights grounded in religion, morality, and community service.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent artist =
this.CreateChatCompletionAgent(
name: "Artist",
description: "An artist from Africa.",
instructions:
"""
You're an artist from Africa.
You view life through creative expression, storytelling, and collective memory.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent immigrant =
this.CreateChatCompletionAgent(
name: "Immigrant",
description: "An immigrant entrepreneur from Asia living in Canada.",
instructions:
"""
You're an immigrant entrepreneur from Asia living in Canada.
You balance trandition with adaption.
You focus on family success, risk, and opportunity.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent doctor =
this.CreateChatCompletionAgent(
name: "Doctor",
description: "A doctor from Scandinavia.",
instructions:
"""
You're a doctor from Scandinavia.
Your perspective is shaped by public health, equity, and structured societal support.
You are in a debate. Feel free to challenge the other participants with respect.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
const string topic = "What does a good life mean to you personally?";
Kernel kernel = this.CreateKernelWithChatCompletion();
GroupChatOrchestration orchestration =
new(
new AIGroupChatManager(
topic,
kernel.GetRequiredService<IChatCompletionService>())
{
MaximumInvocationCount = 5
},
farmer,
developer,
teacher,
activist,
spiritual,
artist,
immigrant,
doctor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
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();
}
private sealed class AIGroupChatManager(string topic, IChatCompletionService chatCompletion) : GroupChatManager
{
private static class Prompts
{
public static string Termination(string topic) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You need to determine if the discussion has reached a conclusion.
If you would like to end the discussion, please respond with True. Otherwise, respond with False.
""";
public static string Selection(string topic, string participants) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You need to select the next participant to speak.
Here are the names and descriptions of the participants:
{participants}\n
Please respond with only the name of the participant you would like to select.
""";
public static string Filter(string topic) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You have just concluded the discussion.
Please summarize the discussion and provide a closing statement.
""";
}
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default) =>
this.GetResponseAsync<string>(history, Prompts.Filter(topic), cancellationToken);
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<string>> SelectNextAgent(ChatHistory history, GroupChatTeam team, CancellationToken cancellationToken = default) =>
this.GetResponseAsync<string>(history, Prompts.Selection(topic, team.FormatList()), cancellationToken);
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default) =>
ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "The AI group chat manager does not request user input." });
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
result = await this.GetResponseAsync<bool>(history, Prompts.Termination(topic), cancellationToken);
}
return result;
}
private async ValueTask<GroupChatManagerResult<TValue>> GetResponseAsync<TValue>(ChatHistory history, string prompt, CancellationToken cancellationToken = default)
{
OpenAIPromptExecutionSettings executionSettings = new() { ResponseFormat = typeof(GroupChatManagerResult<TValue>) };
ChatHistory request = [.. history, new ChatMessageContent(AuthorRole.System, prompt)];
ChatMessageContent response = await chatCompletion.GetChatMessageContentAsync(request, executionSettings, kernel: null, cancellationToken);
string responseText = response.ToString();
return
JsonSerializer.Deserialize<GroupChatManagerResult<TValue>>(responseText) ??
throw new InvalidOperationException($"Failed to parse response: {responseText}");
}
}
}
@@ -0,0 +1,123 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="HandoffOrchestration"/> that represents
/// a customer support triage system.The orchestration consists of 4 agents, each specialized
/// in a different area of customer support: triage, refunds, order status, and order returns.
/// </summary>
public class Step04_Handoff(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task OrderSupportAsync(bool streamedResponse)
{
// Define the agents & tools
ChatCompletionAgent triageAgent =
this.CreateChatCompletionAgent(
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
ChatCompletionAgent statusAgent =
this.CreateChatCompletionAgent(
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.");
statusAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderStatusPlugin()));
ChatCompletionAgent returnAgent =
this.CreateChatCompletionAgent(
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.");
returnAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderReturnPlugin()));
ChatCompletionAgent refundAgent =
this.CreateChatCompletionAgent(
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.");
refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define user responses for InteractiveCallback (since sample is not interactive)
Queue<string> responses = new();
string task = "I am a customer that needs help with my orders";
responses.Enqueue("I'd like to track the status of my order");
responses.Enqueue("My order ID is 123");
responses.Enqueue("I want to return another order of mine");
responses.Enqueue("Order ID 321");
responses.Enqueue("Broken item");
responses.Enqueue("No, bye");
// Define the orchestration
HandoffOrchestration orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.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)
{
InteractiveCallback = () =>
{
string input = responses.Dequeue();
Console.WriteLine($"\n# INPUT: {input}\n");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{task}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(300));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class OrderStatusPlugin
{
[KernelFunction]
public string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
}
private sealed class OrderReturnPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
}
private sealed class OrderRefundPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
}
}
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="HandoffOrchestration"/>.
/// </summary>
public class Step04a_HandoffWithStructuredInput(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task HandoffStructuredInputAsync()
{
// Initialize plugin
GithubPlugin githubPlugin = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromObject(githubPlugin);
// Define the agents
ChatCompletionAgent triageAgent =
this.CreateChatCompletionAgent(
instructions: "Given a GitHub issue, triage it.",
name: "TriageAgent",
description: "An agent that triages GitHub issues");
ChatCompletionAgent pythonAgent =
this.CreateChatCompletionAgent(
instructions: "You are an agent that handles Python related GitHub issues.",
name: "PythonAgent",
description: "An agent that handles Python related issues");
pythonAgent.Kernel.Plugins.Add(plugin);
ChatCompletionAgent dotnetAgent =
this.CreateChatCompletionAgent(
instructions: "You are an agent that handles .NET related GitHub issues.",
name: "DotNetAgent",
description: "An agent that handles .NET related issues");
dotnetAgent.Kernel.Plugins.Add(plugin);
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
HandoffOrchestration<GithubIssue, string> orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, dotnetAgent, pythonAgent),
triageAgent,
pythonAgent,
dotnetAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
GithubIssue input =
new()
{
Id = "12345",
Title = "Bug: SQLite Error 1: 'ambiguous column name:' when including VectorStoreRecordKey in VectorSearchOptions.Filter",
Body =
"""
Describe the bug
When using column names marked as [VectorStoreRecordData(IsFilterable = true)] in VectorSearchOptions.Filter, the query runs correctly.
However, using the column name marked as [VectorStoreRecordKey] in VectorSearchOptions.Filter, the query throws exception 'SQLite Error 1: ambiguous column name: StartUTC'.
To Reproduce
Add a filter for the column marked [VectorStoreRecordKey]. Since that same column exists in both the vec_TestTable and TestTable, the data for both columns cannot be returned.
Expected behavior
The query should explicitly list the vec_TestTable column names to retrieve and should omit the [VectorStoreRecordKey] column since it will be included in the primary TestTable columns.
Platform
Microsoft.SemanticKernel.Connectors.Sqlite v1.46.0-preview
Additional context
Normal DBContext logging shows only normal context queries. Queries run by VectorizedSearchAsync() don't appear in those logs and I could not find a way to enable logging in semantic search so that I could actually see the exact query that is failing. It would have been very useful to see the failing semantic query.
""",
Labels = []
};
// Start the runtime
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}");
Console.WriteLine($"\n# LABELS: {string.Join(",", githubPlugin.Labels["12345"])}\n");
await runtime.RunUntilIdleAsync();
}
private sealed class GithubIssue
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
[JsonPropertyName("body")]
public string Body { get; set; } = string.Empty;
[JsonPropertyName("labels")]
public string[] Labels { get; set; } = [];
}
private sealed class GithubPlugin
{
public Dictionary<string, string[]> Labels { get; } = [];
[KernelFunction]
public void AddLabels(string issueId, params string[] labels)
{
this.Labels[issueId] = labels;
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.Magentic;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="MagenticOrchestration"/> with two agents:
/// - A Research agent that can perform web searches
/// - A Coder agent that can run code using the code interpreter
/// </summary>
public class Step05_Magentic(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
private const string ManagerModel = "o3-mini";
private const string ResearcherModel = "gpt-4o-search-preview";
/// <summary>
/// Require OpenAI services in order to use "gpt-4o-search-preview" model
/// </summary>
protected override bool ForceOpenAI => true;
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task MagenticTaskAsync(bool streamedResponse)
{
// Define the agents
Kernel researchKernel = CreateKernelWithOpenAIChatCompletion(ResearcherModel);
ChatCompletionAgent researchAgent =
this.CreateChatCompletionAgent(
name: "ResearchAgent",
description: "A helpful assistant with access to web search. Ask it to perform web searches.",
instructions: "You are a Researcher. You find information without additional computation or quantitative analysis.",
kernel: researchKernel);
PersistentAgentsClient agentsClient = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
PersistentAgent definition =
await agentsClient.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
name: "CoderAgent",
description: "Write and executes code to process and analyze data.",
instructions: "You solve questions using code. Please provide detailed analysis and computation process.",
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent coderAgent = new(definition, agentsClient);
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
Kernel managerKernel = this.CreateKernelWithChatCompletion(ManagerModel);
StandardMagenticManager manager =
new(managerKernel.GetRequiredService<IChatCompletionService>(), new OpenAIPromptExecutionSettings())
{
MaximumInvocationCount = 5,
};
MagenticOrchestration orchestration =
new(manager, researchAgent, coderAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input =
"""
I am preparing a report on the energy efficiency of different machine learning model architectures.
Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 on standard datasets
(e.g., ImageNet for ResNet, GLUE for BERT, WebText for GPT-2).
Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 VM for 24 hours.
Provide tables for clarity, and recommend the most energy-efficient model per task type
(image classification, text classification, and text generation).
""";
Console.WriteLine($"\n# INPUT:\n{input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 20));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private Kernel CreateKernelWithOpenAIChatCompletion(string model)
{
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
model,
TestConfiguration.OpenAI.ApiKey);
return builder.Build();
}
}
@@ -0,0 +1,332 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Magentic;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="MagenticOrchestration"/> with two agents:
/// - A Research agent that can perform web searches
/// - A Coder agent that can run code using the code interpreter
/// </summary>
public class Step06_DifferentAgentTypes(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task ConcurrentOrchestrationAsync()
{
// Define the agents
Agent physicist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in physics. You answer questions from a physics perspective.",
name: "Physicist",
description: "An expert in physics");
Agent chemist =
await this.CreateAzureAIAgentAsync(
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
name: "Chemist",
description: "An expert in chemistry");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
ConcurrentOrchestration orchestration =
new(physicist, chemist)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
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);
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task SequentialOrchestrationAsync()
{
// Define the agents
Agent analystAgent =
this.CreateChatCompletionAgent(
name: "Analyst",
instructions:
"""
You are a marketing analyst. Given a product description, identify:
- Key features
- Target audience
- Unique selling points
""",
description: "A agent that extracts key concepts from a product description.");
Agent writerAgent =
await this.CreateOpenAIAssistantAgentAsync(
name: "copywriter",
instructions:
"""
You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
compose a compelling marketing copy (like a newsletter section) that highlights these points.
Output should be short (around 150 words), output just the copy as a single text block.
""",
description: "An agent that writes a marketing copy based on the extracted concepts.");
Agent editorAgent =
await this.CreateAzureAIAgentAsync(
name: "editor",
instructions:
"""
You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
give format and make it polished. Output the final improved copy as a single text block.
""",
description: "An agent that formats and proofreads the marketing copy.");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
SequentialOrchestration orchestration =
new(analystAgent, writerAgent, editorAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
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 * 2));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task GroupChatOrchestrationAsync()
{
// Define the agents
Agent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
Agent editor =
await this.CreateOpenAIAssistantAgentAsync(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
GroupChatOrchestration orchestration =
new(new AuthorCriticManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input = "Create a slogan for a new electric 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();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task HandoffOrchestrationAsync()
{
// Define the agents & tools
Agent triageAgent =
this.CreateChatCompletionAgent(
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
Agent statusAgent =
this.CreateChatCompletionAgent(
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.");
statusAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderStatusPlugin()));
Agent returnAgent =
this.CreateChatCompletionAgent(
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.");
returnAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderReturnPlugin()));
Agent refundAgent =
this.CreateChatCompletionAgent(
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.");
refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define user responses for InteractiveCallback (since sample is not interactive)
Queue<string> responses = new();
string task = "I am a customer that needs help with my orders";
responses.Enqueue("I'd like to track the status of my order");
responses.Enqueue("My order ID is 123");
responses.Enqueue("I want to return another order of mine");
responses.Enqueue("Order ID 321");
responses.Enqueue("Broken item");
responses.Enqueue("No, bye");
// Define the orchestration
HandoffOrchestration orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.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)
{
InteractiveCallback = () =>
{
string input = responses.Dequeue();
Console.WriteLine($"\n# INPUT: {input}\n");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{task}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 10));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class OrderStatusPlugin
{
[KernelFunction]
public string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
}
private sealed class OrderReturnPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
}
private sealed class OrderRefundPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
}
private sealed class AuthorCriticManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The reviewer has approved the copy." };
}
}
return result;
}
}
}