chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -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." });
|
||||
}
|
||||
}
|
||||
}
|
||||
+214
@@ -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.";
|
||||
}
|
||||
}
|
||||
+125
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user