mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Port Agent Orchestration (#107)
* Checkpoint * Checkpoint * Namespaces * Namespace * Cleanup * Namespace order * Fix sync * Formatting * Formatting * Namespace * Namespace order * Code convention * Naming * Naming * Text handling * Text handling * Namespace * Namespace order * Namespace ordering * Test * ValueTask * net472 * Test fix * Fix namespace (net472) * Namespace * Fix conditional namespace * Fix type expression * Compatibility and cleanup * Sample compatibility * Sample compat * Test compat * modifier order * Simply http-stub * Formating fix for unit-test * Fix test * Real fix * Test clean-up * Update dotnet/src/Microsoft.Agents.Orchestration/Handoff/HandoffOrchestration.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix build errors after merging --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
co-authored by
Copilot
Stephen Toub
parent
35c938fb5b
commit
7c8ec5ec19
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Concurrent;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/>
|
||||
/// for executing multiple agents on the same task in parallel.
|
||||
/// </summary>
|
||||
public class ConcurrentOrchestration_Intro(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOrchestrationAsync(bool streamedResponse)
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent physicist =
|
||||
this.CreateAgent(
|
||||
instructions: "You are an expert in physics. You answer questions from a physics perspective.",
|
||||
description: "An expert in physics");
|
||||
ChatClientAgent chemist =
|
||||
this.CreateAgent(
|
||||
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
|
||||
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
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
string input = "What is temperature?";
|
||||
Console.WriteLine($"\n# INPUT: {input}\n");
|
||||
OrchestrationResult<string[]> result = await orchestration.InvokeAsync(input, runtime);
|
||||
|
||||
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
|
||||
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Concurrent;
|
||||
using Microsoft.Agents.Orchestration.Transforms;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.Shared.Samples;
|
||||
|
||||
namespace Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/> with structured output.
|
||||
/// </summary>
|
||||
public class ConcurrentOrchestration_With_StructuredOutput(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
|
||||
|
||||
[Fact]
|
||||
public async Task RunOrchestrationAsync()
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent agent1 =
|
||||
this.CreateAgent(
|
||||
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");
|
||||
ChatClientAgent agent2 =
|
||||
this.CreateAgent(
|
||||
instructions: "You are an expert in sentiment analysis. Given an article, identify the sentiment.",
|
||||
description: "An expert in sentiment analysis");
|
||||
ChatClientAgent agent3 =
|
||||
this.CreateAgent(
|
||||
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
|
||||
StructuredOutputTransform<Analysis> outputTransform = new(this.CreateChatClient());
|
||||
ConcurrentOrchestration<string, Analysis> orchestration =
|
||||
new(agent1, agent2, agent3)
|
||||
{
|
||||
LoggerFactory = this.LoggerFactory,
|
||||
ResultTransform = outputTransform.TransformAsync,
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
const string resourceId = "Hamlet_full_play_summary.txt";
|
||||
string input = Resources.Read(resourceId);
|
||||
Console.WriteLine($"\n# INPUT: @{resourceId}\n");
|
||||
OrchestrationResult<Analysis> result = await orchestration.InvokeAsync(input, runtime);
|
||||
|
||||
Analysis output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
|
||||
Console.WriteLine($"\n# RESULT:\n{JsonSerializer.Serialize(output, s_options)}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
}
|
||||
|
||||
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
|
||||
private sealed class Analysis
|
||||
{
|
||||
public IList<string> Themes { get; set; } = [];
|
||||
public IList<string> Sentiments { get; set; } = [];
|
||||
public IList<string> Entities { get; set; } = [];
|
||||
}
|
||||
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.GroupChat;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace 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 GroupChatOrchestration_Intro(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOrchestrationAsync(bool streamedResponse)
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent writer =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent editor =
|
||||
this.CreateAgent(
|
||||
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 that it is approved.
|
||||
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 RoundRobinGroupChatManager()
|
||||
{
|
||||
MaximumInvocationCount = 5
|
||||
},
|
||||
writer,
|
||||
editor)
|
||||
{
|
||||
LoggerFactory = this.LoggerFactory,
|
||||
ResponseCallback = monitor.ResponseCallback,
|
||||
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
|
||||
Console.WriteLine($"\n# INPUT: {input}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.GroupChat;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace 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 GroupChatOrchestration_With_AIManager(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunOrchestrationAsync()
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent farmer =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent developer =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent teacher =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent activist =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent spiritual =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent artist =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent immigrant =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent doctor =
|
||||
this.CreateAgent(
|
||||
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?";
|
||||
GroupChatOrchestration orchestration =
|
||||
new(
|
||||
new AIGroupChatManager(
|
||||
topic,
|
||||
this.CreateChatClient())
|
||||
{
|
||||
MaximumInvocationCount = 5
|
||||
},
|
||||
farmer,
|
||||
developer,
|
||||
teacher,
|
||||
activist,
|
||||
spiritual,
|
||||
artist,
|
||||
immigrant,
|
||||
doctor)
|
||||
{
|
||||
LoggerFactory = this.LoggerFactory,
|
||||
ResponseCallback = monitor.ResponseCallback,
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
Console.WriteLine($"\n# INPUT: {topic}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(topic, runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
|
||||
private sealed class AIGroupChatManager(string topic, IChatClient chatClient) : 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(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default) =>
|
||||
this.GetResponseAsync<string>(history, Prompts.Filter(topic), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<GroupChatManagerResult<string>> SelectNextAgent(IReadOnlyCollection<ChatMessage> history, GroupChatTeam team, CancellationToken cancellationToken = default) =>
|
||||
this.GetResponseAsync<string>(history, Prompts.Selection(topic, team.FormatList()), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default) =>
|
||||
new(new GroupChatManagerResult<bool>(false) { Reason = "The AI group chat manager does not request user input." });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(IReadOnlyCollection<ChatMessage> 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>(IReadOnlyCollection<ChatMessage> history, string prompt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatResponse<GroupChatManagerResult<TValue>> response = await chatClient.GetResponseAsync<GroupChatManagerResult<TValue>>([.. history, new ChatMessage(ChatRole.System, prompt)], new ChatOptions { ToolMode = ChatToolMode.Auto }, useJsonSchemaResponseFormat: true, cancellationToken);
|
||||
return response.Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.GroupChat;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/> with human in the loop
|
||||
/// </summary>
|
||||
public class GroupChatOrchestration_With_HumanInTheLoop(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunOrchestrationAsync()
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent writer =
|
||||
this.CreateAgent(
|
||||
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.
|
||||
""");
|
||||
ChatClientAgent editor =
|
||||
this.CreateAgent(
|
||||
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 that it is approved.
|
||||
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 CustomRoundRobinGroupChatManager()
|
||||
{
|
||||
MaximumInvocationCount = 5,
|
||||
InteractiveCallback = () =>
|
||||
{
|
||||
ChatMessage input = new(ChatRole.User, "I like it");
|
||||
monitor.History.Add(input);
|
||||
Console.WriteLine($"\n# INPUT: {input.Text}\n");
|
||||
return new ValueTask<ChatMessage>(input);
|
||||
}
|
||||
},
|
||||
writer,
|
||||
editor)
|
||||
{
|
||||
LoggerFactory = this.LoggerFactory,
|
||||
ResponseCallback = monitor.ResponseCallback,
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
|
||||
Console.WriteLine($"\n# INPUT: {input}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
|
||||
/// <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 CustomRoundRobinGroupChatManager : RoundRobinGroupChatManager
|
||||
{
|
||||
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(IReadOnlyCollection<ChatMessage> history, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string? lastAgent = history.LastOrDefault()?.AuthorName;
|
||||
|
||||
GroupChatManagerResult<bool> result;
|
||||
|
||||
if (lastAgent is null)
|
||||
{
|
||||
result = new GroupChatManagerResult<bool>(false) { Reason = "No agents have spoken yet." };
|
||||
}
|
||||
|
||||
if (lastAgent == "Reviewer")
|
||||
{
|
||||
result = new GroupChatManagerResult<bool>(true) { Reason = "User input is needed after the reviewer's message." };
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new GroupChatManagerResult<bool>(false) { Reason = "User input is not needed until the reviewer's message." };
|
||||
}
|
||||
|
||||
return new ValueTask<GroupChatManagerResult<bool>>(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Handoff;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace 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 HandoffOrchestration_Intro(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOrchestrationAsync(bool streamedResponse)
|
||||
{
|
||||
// Define the agents & tools
|
||||
ChatClientAgent triageAgent =
|
||||
this.CreateAgent(
|
||||
instructions: "A customer support agent that triages issues.",
|
||||
name: "TriageAgent",
|
||||
description: "Handle customer requests.");
|
||||
ChatClientAgent statusAgent =
|
||||
this.CreateAgent(
|
||||
name: "OrderStatusAgent",
|
||||
instructions: "Handle order status requests.",
|
||||
description: "A customer support agent that checks order status.",
|
||||
functions: AIFunctionFactory.Create(OrderFunctions.CheckOrderStatus));
|
||||
ChatClientAgent returnAgent =
|
||||
this.CreateAgent(
|
||||
name: "OrderReturnAgent",
|
||||
instructions: "Handle order return requests.",
|
||||
description: "A customer support agent that handles order returns.",
|
||||
functions: AIFunctionFactory.Create(OrderFunctions.ProcessReturn));
|
||||
ChatClientAgent refundAgent =
|
||||
this.CreateAgent(
|
||||
name: "OrderRefundAgent",
|
||||
instructions: "Handle order refund requests.",
|
||||
description: "A customer support agent that handles order refund.",
|
||||
functions: AIFunctionFactory.Create(OrderFunctions.ProcessRefund));
|
||||
|
||||
// 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 text = responses.Dequeue();
|
||||
ChatMessage input = new(ChatRole.User, text);
|
||||
monitor.History.Add(input);
|
||||
Console.WriteLine($"\n# INPUT: {input.Text}\n");
|
||||
return new(input);
|
||||
},
|
||||
LoggerFactory = this.LoggerFactory,
|
||||
ResponseCallback = monitor.ResponseCallback,
|
||||
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
Console.WriteLine($"\n# INPUT:\n{task}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
|
||||
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(300));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
|
||||
private static class OrderFunctions
|
||||
{
|
||||
public static string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
|
||||
public static string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
|
||||
public static string ProcessRefund(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Handoff;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use the <see cref="HandoffOrchestration"/>.
|
||||
/// </summary>
|
||||
public class HandoffOrchestration_With_StructuredInput(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunOrchestrationAsync()
|
||||
{
|
||||
// Initialize plugin
|
||||
GithubPlugin githubPlugin = new();
|
||||
AIFunction githubAddLabelFunction = AIFunctionFactory.Create(githubPlugin.AddLabels);
|
||||
|
||||
// Define the agents
|
||||
ChatClientAgent triageAgent =
|
||||
this.CreateAgent(
|
||||
instructions: "Given a GitHub issue, triage it.",
|
||||
name: "TriageAgent",
|
||||
description: "An agent that triages GitHub issues");
|
||||
ChatClientAgent pythonAgent =
|
||||
this.CreateAgent(
|
||||
instructions: "You are an agent that handles Python related GitHub issues.",
|
||||
name: "PythonAgent",
|
||||
description: "An agent that handles Python related issues",
|
||||
functions: githubAddLabelFunction);
|
||||
ChatClientAgent dotnetAgent =
|
||||
this.CreateAgent(
|
||||
instructions: "You are an agent that handles .NET related GitHub issues.",
|
||||
name: "DotNetAgent",
|
||||
description: "An agent that handles .NET related issues",
|
||||
functions: githubAddLabelFunction);
|
||||
|
||||
// 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
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
Console.WriteLine($"\n# INPUT:\n{input.Id}: {input.Title}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
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; } = [];
|
||||
|
||||
public void AddLabels(string issueId, params string[] labels)
|
||||
{
|
||||
this.Labels[issueId] = labels;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Sequential;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace 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 SequentialOrchestration_Intro(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOrchestrationAsync(bool streamedResponse)
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent analystAgent =
|
||||
this.CreateAgent(
|
||||
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.");
|
||||
ChatClientAgent writerAgent =
|
||||
this.CreateAgent(
|
||||
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.");
|
||||
ChatClientAgent editorAgent =
|
||||
this.CreateAgent(
|
||||
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
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
|
||||
Console.WriteLine($"\n# INPUT: {input}\n");
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
|
||||
this.DisplayHistory(monitor.History);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Agents.Orchestration.Sequential;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
namespace Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use cancel a <see cref="SequentialOrchestration"/> while its running.
|
||||
/// </summary>
|
||||
public class SequentialOrchestration_With_Cancellation(ITestOutputHelper output) : OrchestrationSample(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunOrchestrationAsync()
|
||||
{
|
||||
// Define the agents
|
||||
ChatClientAgent agent =
|
||||
this.CreateAgent(
|
||||
"""
|
||||
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
|
||||
await using InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
string input = "42";
|
||||
Console.WriteLine($"\n# INPUT: {input}\n");
|
||||
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
|
||||
|
||||
result.Cancel();
|
||||
await Task.Delay(TimeSpan.FromSeconds(3));
|
||||
|
||||
try
|
||||
{
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
|
||||
Console.WriteLine($"\n# RESULT: {text}");
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
Console.WriteLine($"\n# CANCELLED: {exception.InnerException?.Message}");
|
||||
}
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user