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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using A2A;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.A2A;
namespace GettingStarted.A2A;
/// <summary>
/// This example demonstrates similarity between using <see cref="A2AAgent"/>
/// and other agent types.
/// </summary>
public class Step01_A2AAgent(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Fact]
public async Task UseA2AAgent()
{
// Create an A2A agent instance
var url = TestConfiguration.A2A.AgentUrl;
using var httpClient = CreateHttpClient();
var client = new A2AClient(url, httpClient);
var cardResolver = new A2ACardResolver(url, httpClient);
var agentCard = await cardResolver.GetAgentCardAsync();
Console.WriteLine(JsonSerializer.Serialize(agentCard, s_jsonSerializerOptions));
var agent = new A2AAgent(client, agentCard);
// Invoke the A2A agent
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync("List the latest invoices for Contoso?"))
{
this.WriteAgentChatMessage(response);
}
}
[Fact]
public async Task UseA2AAgentStreaming()
{
// Create an A2A agent instance
var url = TestConfiguration.A2A.AgentUrl;
using var httpClient = CreateHttpClient();
var client = new A2AClient(url, httpClient);
var cardResolver = new A2ACardResolver(url, httpClient);
var agentCard = await cardResolver.GetAgentCardAsync();
Console.WriteLine(JsonSerializer.Serialize(agentCard, s_jsonSerializerOptions));
var agent = new A2AAgent(client, agentCard);
// Invoke the A2A agent
var responseItems = agent.InvokeStreamingAsync("List the latest invoices for Contoso?");
await WriteAgentStreamMessageAsync(responseItems);
}
#region private
private bool EnableLogging { get; set; } = false;
private HttpClient CreateHttpClient()
{
if (this.EnableLogging)
{
var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
return new HttpClient(handler);
}
return new HttpClient();
}
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new() { WriteIndented = true };
#endregion
}
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Resources;
namespace GettingStarted.AzureAgents;
/// <summary>
/// This example demonstrates similarity between using <see cref="AzureAIAgent"/>
/// and other agent types.
/// </summary>
public class Step01_AzureAIAgent(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseTemplateForAzureAgent()
{
// Define the agent
string generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml);
// Instructions, Name and Description properties defined via the PromptTemplateConfig.
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(TestConfiguration.AzureAI.ChatModelId, templateConfig.Name, templateConfig.Description, templateConfig.Template);
AzureAIAgent agent = new(
definition,
this.Client,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
{
Arguments = new()
{
{ "topic", "Dog" },
{ "length", "3" }
}
};
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
try
{
// Invoke the agent with the default arguments.
await InvokeAgentAsync();
// Invoke the agent with the override arguments.
await InvokeAgentAsync(
new()
{
{ "topic", "Cat" },
{ "length", "3" },
});
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the response.
async Task InvokeAgentAsync(KernelArguments? arguments = null)
{
await foreach (ChatMessageContent response in agent.InvokeAsync(thread, new() { KernelArguments = arguments }))
{
WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,141 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate creation of <see cref="AzureAIAgent"/> with a <see cref="KernelPlugin"/>,
/// and then eliciting its response to explicit user messages.
/// </summary>
public class Step02_AzureAIAgent_Plugins(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseAzureAgentWithPlugin()
{
// Define the agent
AzureAIAgent agent = await CreateAzureAgentAsync(
plugin: KernelPluginFactory.CreateFromType<MenuPlugin>(),
instructions: "Answer questions about the menu.",
name: "Host");
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync(agent, thread, "Hello");
await InvokeAgentAsync(agent, thread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, thread, "What is the special drink and its price?");
await InvokeAgentAsync(agent, thread, "Thank you");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
public async Task UseAzureAgentWithPluginEnumParameter()
{
// Define the agent
AzureAIAgent agent = await CreateAzureAgentAsync(plugin: KernelPluginFactory.CreateFromType<WidgetFactory>());
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
public async Task UseAzureAgentWithPromptFunction()
{
// Define prompt function
KernelFunction promptFunction =
KernelFunctionFactory.CreateFromPrompt(
promptTemplate:
"""
Count the number of vowels in INPUT and report as a markdown table.
INPUT:
{{$input}}
""",
description: "Counts the number of vowels");
// Define the agent
AzureAIAgent agent =
await CreateAzureAgentAsync(
KernelPluginFactory.CreateFromFunctions("AgentPlugin", [promptFunction]),
instructions: "You job is to only and always analyze the vowels in the user input without confirmation.");
// Add a filter to the agent's kernel to log function invocations.
agent.Kernel.FunctionInvocationFilters.Add(new PromptFunctionFilter());
// Create the chat history thread to capture the agent interaction.
AzureAIAgentThread thread = new(agent.Client);
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Who would know naught of art must learn, act, and then take his ease.");
}
private async Task<AzureAIAgent> CreateAzureAgentAsync(KernelPlugin plugin, string? instructions = null, string? name = null)
{
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
name,
null,
instructions);
AzureAIAgent agent =
new(definition, this.Client)
{
Kernel = this.CreateKernelWithChatCompletion(),
};
// Add to the agent's Kernel
if (plugin != null)
{
agent.Kernel.Plugins.Add(plugin);
}
return agent;
}
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(AzureAIAgent agent, AgentThread thread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
private sealed class PromptFunctionFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"\nINVOKING: {context.Function.Name}");
await next.Invoke(context);
System.Console.WriteLine($"\nRESULT: {context.Result}");
}
}
}
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate using code-interpreter on <see cref="AzureAIAgent"/> .
/// </summary>
public class Step03_AzureAIAgent_Vision(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseImageContentWithAgent()
{
// Upload an image
await using Stream imageStream = EmbeddedResource.ReadStream("cat.jpg")!;
PersistentAgentFileInfo fileInfo = await this.Client.Files.UploadFileAsync(imageStream, PersistentAgentFilePurpose.Agents, "cat.jpg");
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(TestConfiguration.AzureAI.ChatModelId);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread thread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
// Refer to public image by url
await InvokeAgentAsync(CreateMessageWithImageUrl("Describe this image.", "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/New_york_times_square-terabass.jpg/1200px-New_york_times_square-terabass.jpg"));
await InvokeAgentAsync(CreateMessageWithImageUrl("What are is the main color in this image?", "https://upload.wikimedia.org/wikipedia/commons/5/56/White_shark.jpg"));
// Refer to uploaded image by file-id.
await InvokeAgentAsync(CreateMessageWithImageReference("Is there an animal in this image?", fileInfo.Id));
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
await this.Client.Files.DeleteFileAsync(fileInfo.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(ChatMessageContent input)
{
this.WriteAgentChatMessage(input);
await foreach (ChatMessageContent response in agent.InvokeAsync(input, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
private ChatMessageContent CreateMessageWithImageUrl(string input, string url)
=> new(AuthorRole.User, [new TextContent(input), new ImageContent(new Uri(url))]);
private ChatMessageContent CreateMessageWithImageReference(string input, string fileId)
=> new(AuthorRole.User, [new TextContent(input), new FileReferenceContent(fileId)]);
}
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate using code-interpreter on <see cref="AzureAIAgent"/> .
/// </summary>
public class Step04_AzureAIAgent_CodeInterpreter(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseCodeInterpreterToolWithAgent()
{
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that are less than the value of 101?");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate using <see cref="AzureAIAgent"/> with file search.
/// </summary>
public class Step05_AzureAIAgent_FileSearch(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseFileSearchToolWithAgent()
{
// Define the agent
await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!;
PersistentAgentFileInfo fileInfo = await this.Client.Files.UploadFileAsync(stream, PersistentAgentFilePurpose.Agents, "employees.pdf");
PersistentAgentsVectorStore fileStore =
await this.Client.VectorStores.CreateVectorStoreAsync(
[fileInfo.Id],
metadata: new Dictionary<string, string>() { { SampleMetadataKey, bool.TrueString } });
PersistentAgent agentModel = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new FileSearchToolDefinition()],
toolResources: new()
{
FileSearch = new()
{
VectorStoreIds = { fileStore.Id },
}
},
metadata: new Dictionary<string, string>() { { SampleMetadataKey, bool.TrueString } });
AzureAIAgent agent = new(agentModel, this.Client);
// Create a thread associated for the agent conversation.
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Who is the youngest employee?");
await InvokeAgentAsync("Who works in sales?");
await InvokeAgentAsync("I have a customer request, who can help me?");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
await this.Client.VectorStores.DeleteVectorStoreAsync(fileStore.Id);
await this.Client.Files.DeleteFileAsync(fileInfo.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace GettingStarted.AzureAgents;
/// <summary>
/// This example demonstrates invoking Open API functions using <see cref="AzureAIAgent" />.
/// </summary>
/// <remarks>
/// Note: Open API invocation does not involve kernel function calling or kernel filters.
/// Azure Function invocation is managed entirely by the Azure AI Agent service.
/// </remarks>
public class Step06_AzureAIAgent_OpenAPI(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseOpenAPIToolWithAgent()
{
// Retrieve Open API specifications
string apiCountries = EmbeddedResource.Read("countries.json");
string apiWeather = EmbeddedResource.Read("weather.json");
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools:
[
new OpenApiToolDefinition("RestCountries", "Retrieve country information", BinaryData.FromString(apiCountries), new OpenApiAnonymousAuthDetails()),
new OpenApiToolDefinition("Weather", "Retrieve weather by location", BinaryData.FromString(apiWeather), new OpenApiAnonymousAuthDetails())
]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("What is the name and population of the country that uses currency with abbreviation THB");
await InvokeAgentAsync("What is the weather in the capitol city of that country?");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
namespace GettingStarted.AzureAgents;
/// <summary>
/// This example demonstrates how to define function tools for an <see cref="AzureAIAgent"/>
/// when the agent is created. This is useful if you want to retrieve the agent later and
/// then dynamically check what function tools it requires.
/// </summary>
public class Step07_AzureAIAgent_Functions(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
private const string HostName = "Host";
private const string HostInstructions = "Answer questions about the menu.";
[Fact]
public async Task UseSingleAgentWithFunctionTools()
{
// Define the agent
// In this sample the function tools are added to the agent this is
// important if you want to retrieve the agent later and then dynamically check
// what function tools it requires.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
var tools = plugin.Select(f => f.ToToolDefinition(plugin.Name));
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
model: TestConfiguration.AzureAI.ChatModelId,
name: HostName,
description: null,
instructions: HostInstructions,
tools: tools);
AzureAIAgent agent = new(definition, this.Client);
// Add plugin to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(plugin);
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup and its price?");
await InvokeAgentAsync("What is the special drink and its price?");
await InvokeAgentAsync("Thank you");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,458 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
namespace GettingStarted.AzureAgents;
/// <summary>
/// This example demonstrates how to declaratively create instances of <see cref="AzureAIAgent"/>.
/// </summary>
public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
{
/// <summary>
/// Demonstrates creating and using a Chat Completion Agent with a Kernel.
/// </summary>
[Fact]
public async Task AzureAIAgentWithConfiguration()
{
var text =
"""
type: foundry_agent
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${AzureAI:ChatModelId}
connection:
connection_string: ${AzureAI:ConnectionString}
""";
AzureAIAgentFactory factory = new();
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(this.Client);
builder.Services.AddSingleton<TokenCredential>(new AzureCliCredential());
var kernel = builder.Build();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million");
}
[Fact]
public async Task AzureAIAgentWithKernel()
{
var text =
"""
type: foundry_agent
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${AzureOpenAI:ChatModelId}
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million");
}
[Fact]
public async Task AzureAIAgentWithId()
{
var text =
"""
id: ${AzureAI:AgentId}
type: foundry_agent
instructions: You are helpful agent who always responds in French.
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(
agent!,
"Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million",
deleteAgent: false);
}
[Fact]
public async Task AzureAIAgentWithCodeInterpreter()
{
var text =
"""
type: foundry_agent
name: CodeInterpreterAgent
instructions: Use the code interpreter tool to answer questions which require code to be generated and executed.
description: Agent with code interpreter tool.
model:
id: ${AzureAI:ChatModelId}
tools:
- type: code_interpreter
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
}
[Fact]
public async Task AzureAIAgentWithFunctions()
{
var text =
"""
type: foundry_agent
name: FunctionCallingAgent
instructions: Use the provided functions to answer questions about the menu.
description: This agent uses the provided functions to answer questions about the menu.
model:
id: ${AzureAI:ChatModelId}
options:
temperature: 0.4
tools:
- id: GetSpecials
type: function
description: Get the specials from the menu.
- id: GetItemPrice
type: function
description: Get the price of an item on the menu.
options:
parameters:
- name: menuItem
type: string
required: true
description: The name of the menu item.
""";
AzureAIAgentFactory factory = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
this._kernel.Plugins.Add(plugin);
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is the special soup and how much does it cost?");
}
[Fact]
public async Task AzureAIAgentWithBingGrounding()
{
var text =
"""
type: foundry_agent
name: BingAgent
instructions: Answer questions using Bing to provide grounding context.
description: This agent answers questions using Bing to provide grounding context.
model:
id: ${AzureAI:ChatModelId}
options:
temperature: 0.4
tools:
- type: bing_grounding
options:
tool_connections:
- ${AzureAI:BingConnectionId}
""";
AzureAIAgentFactory factory = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
this._kernel.Plugins.Add(plugin);
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is the latest new about the Semantic Kernel?");
}
[Fact]
public async Task AzureAIAgentWithFileSearch()
{
var text =
"""
type: foundry_agent
name: FileSearchAgent
instructions: Answer questions using available files to provide grounding context.
description: This agent answers questions using available files to provide grounding context.
model:
id: ${AzureAI:ChatModelId}
optisons:
temperature: 0.4
tools:
- type: file_search
description: Grounding with available files.
options:
vector_store_ids:
- ${AzureAI.VectorStoreId}
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What are the key features of the Semantic Kernel?");
}
[Fact]
public async Task AzureAIAgentWithOpenAPI()
{
var text =
"""
type: foundry_agent
name: WeatherAgent
instructions: Answer questions about the weather. For all other questions politely decline to answer.
description: This agent answers question about the weather.
model:
id: ${AzureAI:ChatModelId}
options:
temperature: 0.4
tools:
- type: openapi
id: GetCurrentWeather
description: Retrieves current weather data for a location based on wttr.in.
options:
specification: |
{
"openapi": "3.1.0",
"info": {
"title": "Get Weather Data",
"description": "Retrieves current weather data for a location based on wttr.in.",
"version": "v1.0.0"
},
"servers": [
{
"url": "https://wttr.in"
}
],
"auth": [],
"paths": {
"/{location}": {
"get": {
"description": "Get weather information for a specific location",
"operationId": "GetCurrentWeather",
"parameters": [
{
"name": "location",
"in": "path",
"description": "City or location to retrieve the weather for",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "format",
"in": "query",
"description": "Always use j1 value for this parameter",
"required": true,
"schema": {
"type": "string",
"default": "j1"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"404": {
"description": "Location not found"
}
},
"deprecated": false
}
}
},
"components": {
"schemes": {}
}
}
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is the current weather in Dublin?");
}
[Fact]
public async Task AzureAIAgentWithOpenAPIYaml()
{
var text =
"""
type: foundry_agent
name: WeatherAgent
instructions: Answer questions about the weather. For all other questions politely decline to answer.
description: This agent answers question about the weather.
model:
id: ${AzureAI:ChatModelId}
options:
temperature: 0.4
tools:
- type: openapi
id: GetCurrentWeather
description: Retrieves current weather data for a location based on wttr.in.
options:
specification:
openapi: "3.1.0"
info:
title: "Get Weather Data"
description: "Retrieves current weather data for a location based on wttr.in."
version: "v1.0.0"
servers:
- url: "https://wttr.in"
auth: []
paths:
/{location}:
get:
description: "Get weather information for a specific location"
operationId: "GetCurrentWeather"
parameters:
- name: "location"
in: "path"
description: "City or location to retrieve the weather for"
required: true
schema:
type: "string"
- name: "format"
in: "query"
description: "Always use j1 value for this parameter"
required: true
schema:
type: "string"
default: "j1"
responses:
"200":
description: "Successful response"
content:
text/plain:
schema:
type: "string"
"404":
description: "Location not found"
deprecated: false
components:
schemes: {}
""";
AzureAIAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is the current weather in Dublin?");
}
[Fact]
public async Task AzureAIAgentWithTemplate()
{
var text =
"""
type: foundry_agent
name: StoryAgent
description: A agent that generates a story about a topic.
instructions: Tell a story about {{$topic}} that is {{$length}} sentences long.
model:
id: ${AzureAI:ChatModelId}
inputs:
topic:
description: The topic of the story.
required: true
default: Cats
length:
description: The number of sentences in the story.
required: true
default: 2
outputs:
output1:
description: output1 description
template:
format: semantic-kernel
""";
AzureAIAgentFactory factory = new();
var promptTemplateFactory = new KernelPromptTemplateFactory();
var agent =
await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot) ??
throw new InvalidOperationException("Unable to create agent");
var options = new AgentInvokeOptions()
{
KernelArguments = new()
{
{ "topic", "Dogs" },
{ "length", "3" },
}
};
Microsoft.SemanticKernel.Agents.AgentThread? agentThread = null;
try
{
await foreach (var response in agent!.InvokeAsync(Array.Empty<ChatMessageContent>(), agentThread, options))
{
agentThread = response.Thread;
this.WriteAgentChatMessage(response);
}
}
finally
{
var azureaiAgent = (AzureAIAgent)agent;
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
public Step08_AzureAIAgent_Declarative(ITestOutputHelper output) : base(output)
{
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(this.Client);
builder.Services.AddSingleton(this.CreateFoundryProjectClient());
this._kernel = builder.Build();
}
#region private
private readonly Kernel _kernel;
/// <summary>
/// Invoke the agent with the user input.
/// </summary>
private async Task InvokeAgentAsync(Agent agent, string input, bool? deleteAgent = true)
{
Microsoft.SemanticKernel.Agents.AgentThread? agentThread = null;
try
{
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, input)))
{
agentThread = response.Thread;
WriteAgentChatMessage(response);
}
}
finally
{
if (deleteAgent ?? true)
{
var azureaiAgent = agent as AzureAIAgent;
Assert.NotNull(azureaiAgent);
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
}
#endregion
}
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate using code-interpreter on <see cref="AzureAIAgent"/> .
/// </summary>
public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseBingGroundingToolWithAgent()
{
// Access the BingGrounding connection
string connectionId = await this.GetConnectionId(TestConfiguration.AzureAI.BingConnectionId);
BingGroundingSearchConfiguration bingToolConfiguration = new(connectionId);
BingGroundingSearchToolParameters bingToolParameters = new([bingToolConfiguration]);
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new BingGroundingToolDefinition(bingToolParameters)]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread thread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
//await InvokeAgentAsync("How does wikipedia explain Euler's Identity?");
await InvokeAgentAsync("What is the current price of gold?");
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
[Fact]
public async Task UseBingGroundingToolWithStreaming()
{
// Access the BingGrounding connection
string connectionId = await this.GetConnectionId(TestConfiguration.AzureAI.BingConnectionId);
BingGroundingSearchConfiguration bingToolConfiguration = new(connectionId);
BingGroundingSearchToolParameters bingToolParameters = new([bingToolConfiguration]);
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new BingGroundingToolDefinition(bingToolParameters)]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread thread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("What is the current price of gold?");
// Display chat history
Console.WriteLine("\n================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
await foreach (ChatMessageContent message in thread.GetMessagesAsync())
{
this.WriteAgentChatMessage(message);
}
}
finally
{
await thread.DeleteAsync();
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
bool isFirst = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, thread))
{
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
if (!string.IsNullOrWhiteSpace(response.Content))
{
Console.WriteLine($"\t> streamed: {response.Content}");
}
foreach (StreamingAnnotationContent? annotation in response.Items.OfType<StreamingAnnotationContent>())
{
Console.WriteLine($"\t {annotation.ReferenceId} - {annotation.Title}");
}
}
}
}
}
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.AzureAgents;
/// <summary>
/// Demonstrate parsing JSON response.
/// </summary>
public class Step10_JsonResponse(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
private const string TutorInstructions =
"""
Think step-by-step and rate the user input on creativity and expressiveness from 1-100.
Respond in JSON format with the following JSON schema:
{
"score": "integer (1-100)",
"notes": "the reason for your score"
}
""";
[Fact]
public async Task UseJsonObjectResponse()
{
PersistentAgent definition =
await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
instructions: TutorInstructions,
responseFormat:
BinaryData.FromString(
"""
{
"type": "json_object"
}
"""));
AzureAIAgent agent = new(definition, this.Client);
await ExecuteAgent(agent);
}
[Fact]
public async Task UseJsonSchemaResponse()
{
PersistentAgent definition =
await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
instructions: TutorInstructions,
responseFormat: BinaryData.FromString(
"""
{
"type": "json_schema",
"json_schema":
{
"type": "object",
"name": "scoring",
"schema": {
"type": "object",
"properties": {
"score": {
"type": "number"
},
"notes": {
"type": "string"
}
},
"required": [
"score",
"notes"
],
"additionalProperties": false
},
"strict": true
}
}
"""));
AzureAIAgent agent = new(definition, this.Client);
await ExecuteAgent(agent);
}
private async Task ExecuteAgent(AzureAIAgent agent)
{
AzureAIAgentThread thread = new(agent.Client);
await InvokeAgentAsync("The sunset is very colorful.");
await InvokeAgentAsync("The sunset is setting over the mountains.");
await InvokeAgentAsync("The sunset is setting over the mountains and filled the sky with a deep red flame, setting the clouds ablaze.");
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,38 @@
# Concept samples on how to use AWS Bedrock agents
## Pre-requisites
1. You need to have an AWS account and [access to the foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-permissions.html)
2. [AWS CLI installed](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and [configured](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration)
## Before running the samples
You need to set up some user secrets to run the samples.
### `BedrockAgent:AgentResourceRoleArn`
On your AWS console, go to the IAM service and go to **Roles**. Find the role you want to use and click on it. You will find the ARN in the summary section.
```
dotnet user-secrets set "BedrockAgent:AgentResourceRoleArn" "arn:aws:iam::...:role/..."
```
### `BedrockAgent:FoundationModel`
You need to make sure you have permission to access the foundation model. You can find the model ID in the [AWS documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html). To see the models you have access to, find the policy attached to your role you should see a list of models you have access to under the `Resource` section.
```
dotnet user-secrets set "BedrockAgent:FoundationModel" "..."
```
### How to add the `bedrock:InvokeModelWithResponseStream` action to an IAM policy
1. Open the [IAM console](https://console.aws.amazon.com/iam/).
2. On the left navigation pane, choose `Roles` under `Access management`.
3. Find the role you want to edit and click on it.
4. Under the `Permissions policies` tab, click on the policy you want to edit.
5. Under the `Permissions defined in this policy` section, click on the service. You should see **Bedrock** if you already have access to the Bedrock agent service.
6. Click on the service, and then click `Edit`.
7. On the right, you will be able to add an action. Find the service and search for `InvokeModelWithResponseStream`.
8. Check the box next to the action and then scroll all the way down and click `Next`.
9. Follow the prompts to save the changes.
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to interact with a <see cref="BedrockAgent"/> in the most basic way.
/// </summary>
public class Step01_BedrockAgent(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
private const string UserQuery = "Why is the sky blue in one sentence?";
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> and interact with it.
/// The agent will respond to the user query.
/// </summary>
[Fact]
public async Task UseNewAgent()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step01_BedrockAgent");
// Respond to user input
AgentThread bedrockAgentThread = new BedrockAgentThread(this.RuntimeClient);
try
{
var responses = bedrockAgent.InvokeAsync(new ChatMessageContent(AuthorRole.User, UserQuery), bedrockAgentThread, null);
await foreach (ChatMessageContent response in responses)
{
this.Output.WriteLine(response.Content);
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
await bedrockAgentThread.DeleteAsync();
}
}
/// <summary>
/// Demonstrates how to use an existing <see cref="BedrockAgent"/> and interact with it.
/// The agent will respond to the user query.
/// </summary>
[Fact]
public async Task UseExistingAgent()
{
// Retrieve the agent
// Replace "bedrock-agent-id" with the ID of the agent you want to use
var agentId = "bedrock-agent-id";
var getAgentResponse = await this.Client.GetAgentAsync(new() { AgentId = agentId });
var bedrockAgent = new BedrockAgent(getAgentResponse.Agent, this.Client, this.RuntimeClient);
// Respond to user input
AgentThread bedrockAgentThread = new BedrockAgentThread(this.RuntimeClient);
try
{
var responses = bedrockAgent.InvokeAsync(new ChatMessageContent(AuthorRole.User, UserQuery), bedrockAgentThread, null);
await foreach (ChatMessageContent response in responses)
{
this.Output.WriteLine(response.Content);
}
}
finally
{
await bedrockAgentThread.DeleteAsync();
}
}
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> and interact with it using streaming.
/// The agent will respond to the user query.
/// </summary>
[Fact]
public async Task UseNewAgentStreaming()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step01_BedrockAgent_Streaming");
AgentThread bedrockAgentThread = new BedrockAgentThread(this.RuntimeClient);
// Respond to user input
try
{
var streamingResponses = bedrockAgent.InvokeStreamingAsync(new ChatMessageContent(AuthorRole.User, UserQuery), bedrockAgentThread, null);
await foreach (StreamingChatMessageContent response in streamingResponses)
{
this.Output.WriteLine(response.Content);
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
await bedrockAgentThread.DeleteAsync();
}
}
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
return new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
}
}
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Reflection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to interact with a <see cref="BedrockAgent"/> with code interpreter enabled.
/// </summary>
public class Step02_BedrockAgent_CodeInterpreter(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
private const string UserQuery = @"Create a bar chart for the following data:
Panda 5
Tiger 8
Lion 3
Monkey 6
Dolphin 2";
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> with code interpreter enabled and interact with it.
/// The agent will respond to the user query by creating a Python code that will be executed by the code interpreter.
/// The output of the code interpreter will be a file containing the bar chart, which will be returned to the user.
/// </summary>
[Fact]
public async Task UseAgentWithCodeInterpreter()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step02_BedrockAgent_CodeInterpreter");
AgentThread bedrockAgentThread = new BedrockAgentThread(this.RuntimeClient);
// Respond to user input
try
{
BinaryContent? binaryContent = null;
var responses = bedrockAgent.InvokeAsync(new ChatMessageContent(AuthorRole.User, UserQuery), bedrockAgentThread, null);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
if (binaryContent == null && response.Items.Count > 0)
{
binaryContent = response.Items.OfType<BinaryContent>().FirstOrDefault();
}
}
if (binaryContent == null)
{
throw new InvalidOperationException("No file found in the response.");
}
// Save the file to the same directory as the test assembly
var filePath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
binaryContent.Metadata!["Name"]!.ToString()!);
this.Output.WriteLine($"Saving file to {filePath}");
binaryContent.WriteToFile(filePath, overwrite: true);
// Expected output:
// Here is the bar chart for the given data:
// [A bar chart showing the following data:
// Panda 5
// Tiger 8
// Lion 3
// Monkey 6
// Dolphin 2]
// Saving file to ...
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
await bedrockAgentThread.DeleteAsync();
}
}
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
var bedrockAgent = new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
// Create the code interpreter action group and prepare the agent for interaction
await bedrockAgent.CreateCodeInterpreterActionGroupAsync();
return bedrockAgent;
}
}
@@ -0,0 +1,227 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to interact with a <see cref="BedrockAgent"/> with kernel functions.
/// </summary>
public class Step03_BedrockAgent_Functions(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> with kernel functions enabled and interact with it.
/// The agent will respond to the user query by calling kernel functions to provide weather information.
/// </summary>
[Fact]
public async Task UseAgentWithFunctions()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions");
// Respond to user input
try
{
var responses = bedrockAgent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, "What is the weather in Seattle?"),
null);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> with kernel functions enabled and interact with it.
/// The agent will respond to the user query by calling kernel functions that returns complex types to provide
/// information about the menu.
/// </summary>
[Fact]
public async Task UseAgentWithFunctionsComplexType()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Complex_Types");
// Respond to user input
try
{
var responses = bedrockAgent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, "What is the special soup and how much does it cost?"),
null);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> with kernel functions enabled and interact with it using streaming.
/// The agent will respond to the user query by calling kernel functions to provide weather information.
/// </summary>
[Fact]
public async Task UseAgentStreamingWithFunctions()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Streaming");
// Respond to user input
try
{
var streamingResponses = bedrockAgent.InvokeStreamingAsync(
new ChatMessageContent(AuthorRole.User, "What is the weather forecast in Seattle?"),
null);
await foreach (StreamingChatMessageContent response in streamingResponses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
/// <summary>
/// Demonstrates how to create a new <see cref="BedrockAgent"/> with kernel functions enabled and interact with it.
/// The agent will respond to the user query by calling multiple kernel functions in parallel to provide weather information.
/// </summary>
[Fact]
public async Task UseAgentWithParallelFunctionsAsync()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step03_BedrockAgent_Functions_Parallel");
// Respond to user input
try
{
var responses = bedrockAgent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, "What is the current weather in Seattle and what is the weather forecast in Seattle?"),
null);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new kernel with plugins
Kernel kernel = new();
kernel.Plugins.Add(KernelPluginFactory.CreateFromType<WeatherPlugin>());
kernel.Plugins.Add(KernelPluginFactory.CreateFromType<MenuPlugin>());
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
var bedrockAgent = new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
// Create the kernel function action group and prepare the agent for interaction
await bedrockAgent.CreateKernelFunctionActionGroupAsync();
return bedrockAgent;
}
private sealed class WeatherPlugin
{
[KernelFunction, Description("Provides real-time weather information.")]
public string Current([Description("The location to get the weather for.")] string location)
{
return $"The current weather in {location} is 72 degrees.";
}
[KernelFunction, Description("Forecast weather information.")]
public string Forecast([Description("The location to get the weather for.")] string location)
{
return $"The forecast for {location} is 75 degrees tomorrow.";
}
}
private sealed class MenuPlugin
{
[KernelFunction, Description("Get the menu.")]
public MenuItem[] GetMenu()
{
return s_menuItems;
}
[KernelFunction, Description("Provides a list of specials from the menu.")]
public MenuItem[] GetSpecials()
{
return [.. s_menuItems.Where(i => i.IsSpecial)];
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public float? GetItemPrice([Description("The name of the menu item.")] string menuItem)
{
return s_menuItems.FirstOrDefault(i => i.Name.Equals(menuItem, StringComparison.OrdinalIgnoreCase))?.Price;
}
private static readonly MenuItem[] s_menuItems =
[
new()
{
Category = "Soup",
Name = "Clam Chowder",
Price = 4.95f,
IsSpecial = true,
},
new()
{
Category = "Soup",
Name = "Tomato Soup",
Price = 4.95f,
IsSpecial = false,
},
new()
{
Category = "Salad",
Name = "Cobb Salad",
Price = 9.99f,
},
new()
{
Category = "Drink",
Name = "Chai Tea",
Price = 2.95f,
IsSpecial = true,
},
];
public sealed class MenuItem
{
public string Category { get; init; }
public string Name { get; init; }
public float Price { get; init; }
public bool IsSpecial { get; init; }
}
}
}
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Amazon.BedrockAgentRuntime.Model;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to interact with a <see cref="BedrockAgent"/> and inspect the agent's thought process.
/// To learn more about different traces available, see:
/// https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html
/// </summary>
public class Step04_BedrockAgent_Trace(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
/// <summary>
/// Demonstrates how to inspect the thought process of a <see cref="BedrockAgent"/> by enabling trace.
/// </summary>
[Fact]
public async Task UseAgentWithTrace()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step04_BedrockAgent_Trace");
// Respond to user input
var userQuery = "What is the current weather in Seattle and what is the weather forecast in Seattle?";
try
{
AgentThread agentThread = new BedrockAgentThread(this.RuntimeClient);
BedrockAgentInvokeOptions options = new()
{
EnableTrace = true,
};
var responses = bedrockAgent.InvokeAsync([new ChatMessageContent(AuthorRole.User, userQuery)], agentThread, options);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
if (response.InnerContent is List<object?> innerContents)
{
// There could be multiple traces and they are stored in the InnerContent property
var traceParts = innerContents.OfType<TracePart>().ToList();
if (traceParts is not null)
{
foreach (var tracePart in traceParts)
{
this.OutputTrace(tracePart.Trace);
}
}
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
/// <summary>
/// Outputs the trace information to the console.
/// This only outputs the orchestration trace for demonstration purposes.
/// To learn more about different traces available, see:
/// https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html
/// </summary>
private void OutputTrace(Trace trace)
{
if (trace.OrchestrationTrace is not null)
{
if (trace.OrchestrationTrace.ModelInvocationInput is not null)
{
this.Output.WriteLine("========== Orchestration trace ==========");
this.Output.WriteLine("Orchestration input:");
this.Output.WriteLine(trace.OrchestrationTrace.ModelInvocationInput.Text);
}
if (trace.OrchestrationTrace.ModelInvocationOutput is not null)
{
this.Output.WriteLine("========== Orchestration trace ==========");
this.Output.WriteLine("Orchestration output:");
this.Output.WriteLine(trace.OrchestrationTrace.ModelInvocationOutput.RawResponse.Content);
this.Output.WriteLine("Usage:");
this.Output.WriteLine($"Input token: {trace.OrchestrationTrace.ModelInvocationOutput.Metadata.Usage.InputTokens}");
this.Output.WriteLine($"Output token: {trace.OrchestrationTrace.ModelInvocationOutput.Metadata.Usage.OutputTokens}");
}
}
// Example output:
// ========== Orchestration trace ==========
// Orchestration input:
// {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ...
// ========== Orchestration trace ==========
// Orchestration output:
// <thinking>
// To answer this question, I will need to call the following functions:
// 1. Step04_BedrockAgent_Trace_KernelFunctions::Current to get the current weather in Seattle
// 2. Step04_BedrockAgent_Trace_KernelFunctions::Forecast to get the weather forecast in Seattle
// </thinking>
//
// <function_calls>
// <invoke>
// <tool_name>Step04_BedrockAgent_Trace_KernelFunctions::Current</tool_name>
// <parameters>
// <location>Seattle</location>
// </parameters>
// Usage:
// Input token: 617
// Output token: 144
// ========== Orchestration trace ==========
// Orchestration input:
// {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ...
// ========== Orchestration trace ==========
// Orchestration output:
// <thinking>Now that I have the current weather in Seattle, I will call the forecast function to get the weather forecast.</thinking>
//
// <function_calls>
// <invoke>
// <tool_name>Step04_BedrockAgent_Trace_KernelFunctions::Forecast</tool_name>
// <parameters>
// <location>Seattle</location>
// </parameters>
// Usage:
// Input token: 834
// Output token: 87
// ========== Orchestration trace ==========
// Orchestration input:
// {"system":"You're a helpful assistant who helps users find information.You have been provided with a set of functions to answer ...
// ========== Orchestration trace ==========
// Orchestration output:
// <answer>
// The current weather in Seattle is 72 degrees. The weather forecast for Seattle is 75 degrees tomorrow.
// Usage:
// Input token: 1003
// Output token: 31
}
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
var bedrockAgent = new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
// Initialize kernel with plugins
bedrockAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<WeatherPlugin>());
// Create the kernel function action group and prepare the agent for interaction
await bedrockAgent.CreateKernelFunctionActionGroupAsync();
return bedrockAgent;
}
private sealed class WeatherPlugin
{
[KernelFunction, Description("Provides realtime weather information.")]
public string Current([Description("The location to get the weather for.")] string location)
{
return $"The current weather in {location} is 72 degrees.";
}
[KernelFunction, Description("Forecast weather information.")]
public string Forecast([Description("The location to get the weather for.")] string location)
{
return $"The forecast for {location} is 75 degrees tomorrow.";
}
}
}
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to interact with a <see cref="BedrockAgent"/> that is associated with a knowledge base.
/// A Bedrock Knowledge Base is a collection of documents that the agent uses to answer user queries.
/// To learn more about Bedrock Knowledge Base, see:
/// https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html
/// </summary>
public class Step05_BedrockAgent_FileSearch(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
// Replace the KnowledgeBaseId with a valid KnowledgeBaseId
// To learn how to create a Knowledge Base, see:
// https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html
private const string KnowledgeBaseId = "[KnowledgeBaseId]";
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
var bedrockAgent = new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
// Associate the agent with a knowledge base and prepare the agent
await bedrockAgent.AssociateAgentKnowledgeBaseAsync(
KnowledgeBaseId,
"You will find information here.");
return bedrockAgent;
}
/// <summary>
/// Demonstrates how to use a <see cref="BedrockAgent"/> with file search.
/// </summary>
[Fact(Skip = "This test is skipped because it requires a valid KnowledgeBaseId.")]
public async Task UseAgentWithFileSearch()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step05_BedrockAgent_FileSearch");
// Respond to user input
// Assuming the knowledge base contains information about Semantic Kernel.
// Feel free to modify the user query according to the information in your knowledge base.
var userQuery = "What is Semantic Kernel?";
try
{
AgentThread bedrockThread = new BedrockAgentThread(this.RuntimeClient);
var responses = bedrockAgent.InvokeAsync(new ChatMessageContent(AuthorRole.User, userQuery), bedrockThread, null, CancellationToken.None);
await foreach (ChatMessageContent response in responses)
{
if (response.Content != null)
{
this.Output.WriteLine(response.Content);
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
}
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how two agents (one of which is a Bedrock agent) can chat with each other.
/// </summary>
public class Step06_BedrockAgent_AgentChat(ITestOutputHelper output) : BaseBedrockAgentTest(output)
{
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
return new BedrockAgent(agentModel, this.Client, this.RuntimeClient);
}
/// <summary>
/// Demonstrates how to put two <see cref="BedrockAgent"/> instances in a chat.
/// </summary>
[Fact]
public async Task UseAgentWithAgentChat()
{
// Create the agent
var bedrockAgent = await this.CreateAgentAsync("Step06_BedrockAgent_AgentChat");
var chatCompletionAgent = new ChatCompletionAgent()
{
Instructions = "You're a translator who helps users understand the content in Spanish.",
Name = "Translator",
Kernel = this.CreateKernelWithChatCompletion(),
};
// Create a chat for agent interaction
var chat = new AgentGroupChat(bedrockAgent, chatCompletionAgent)
{
ExecutionSettings = new()
{
// Terminate after two turns: one from the bedrock agent and one from the chat completion agent.
// Note: each invoke will terminate after two turns, and we are invoking the group chat for each user query.
TerminationStrategy = new MultiTurnTerminationStrategy(2),
}
};
// Respond to user input
string[] userQueries = [
"Why is the sky blue in one sentence?",
"Why do we have seasons in one sentence?"
];
try
{
foreach (var userQuery in userQueries)
{
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, userQuery));
await foreach (var response in chat.InvokeAsync())
{
if (response.Content != null)
{
this.Output.WriteLine($"[{response.AuthorName}]: {response.Content}");
}
}
}
}
finally
{
await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
}
internal sealed class MultiTurnTerminationStrategy : TerminationStrategy
{
public MultiTurnTerminationStrategy(int turns)
{
this.MaximumIterations = turns;
}
/// <inheritdoc/>
protected override Task<bool> ShouldAgentTerminateAsync(
Agent agent,
IReadOnlyList<ChatMessageContent> history,
CancellationToken cancellationToken = default)
{
return Task.FromResult(false);
}
}
}
@@ -0,0 +1,240 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Amazon.BedrockAgent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Bedrock;
namespace GettingStarted.BedrockAgents;
/// <summary>
/// This example demonstrates how to declaratively create instances of <see cref="BedrockAgent"/>.
/// </summary>
public class Step07_BedrockAgent_Declarative : BaseBedrockAgentTest
{
/// <summary>
/// Demonstrates creating and using a Bedrock Agent with using configuration settings.
/// </summary>
[Fact]
public async Task BedrockAgentWithConfiguration()
{
var text =
"""
type: bedrock_agent
name: StoryAgent
description: Story Telling Agent
instructions: Tell a story suitable for children about the topic provided by the user.
model:
id: ${BedrockAgent:FoundationModel}
connection:
type: bedrock
agent_resource_role_arn: ${BedrockAgent:AgentResourceRoleArn}
""";
BedrockAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, configuration: TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Cats and Dogs");
}
/// <summary>
/// Demonstrates loading an existing Bedrock Agent.
/// </summary>
[Fact]
public async Task BedrockAgentWithId()
{
var text =
"""
id: ${BedrockAgent:AgentId}
type: bedrock_agent
""";
BedrockAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, configuration: TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is Semantic Kernel?", false);
}
/// <summary>
/// Demonstrates creating and using a Bedrock Agent with a code interpreter.
/// </summary>
[Fact]
public async Task BedrockAgentWithCodeInterpreter()
{
var text =
"""
type: bedrock_agent
name: CodeInterpreterAgent
instructions: Use the code interpreter tool to answer questions which require code to be generated and executed.
description: Agent with code interpreter tool.
model:
id: ${BedrockAgent:FoundationModel}
connection:
type: bedrock
agent_resource_role_arn: ${BedrockAgent:AgentResourceRoleArn}
tools:
- type: code_interpreter
""";
BedrockAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Use code to determine the values in the Fibonacci sequence that are less then the value of 101?");
}
/// <summary>
/// Demonstrates creating and using a Bedrock Agent with functions.
/// </summary>
[Fact]
public async Task BedrockAgentWithFunctions()
{
var text =
"""
type: bedrock_agent
name: FunctionCallingAgent
instructions: Use the provided functions to answer questions about the menu.
description: This agent uses the provided functions to answer questions about the menu.
model:
id: ${BedrockAgent:FoundationModel}
connection:
type: bedrock
agent_resource_role_arn: ${BedrockAgent:AgentResourceRoleArn}
tools:
- id: Current
type: function
description: Provides real-time weather information.
options:
parameters:
- name: location
type: string
required: true
description: The location to get the weather for.
- id: Forecast
type: function
description: Forecast weather information.
options:
parameters:
- name: location
type: string
required: true
description: The location to get the weather for.
""";
BedrockAgentFactory factory = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<WeatherPlugin>();
this._kernel.Plugins.Add(plugin);
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is the current weather in Seattle and what is the weather forecast in Seattle?");
}
/// <summary>
/// Demonstrates creating and using a Bedrock Agent with a knowledge base.
/// </summary>
[Fact]
public async Task BedrockAgentWithKnowledgeBase()
{
var text =
"""
type: bedrock_agent
name: KnowledgeBaseAgent
instructions: Use the provided knowledge base to answer questions.
description: This agent uses the provided knowledge base to answer questions.
model:
id: ${BedrockAgent:FoundationModel}
connection:
type: bedrock
agent_resource_role_arn: ${BedrockAgent:AgentResourceRoleArn}
tools:
- type: knowledge_base
description: You will find information here.
options:
knowledge_base_id: ${BedrockAgent:KnowledgeBaseId}
""";
BedrockAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What is Semantic Kernel?");
}
public Step07_BedrockAgent_Declarative(ITestOutputHelper output) : base(output)
{
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<AmazonBedrockAgentClient>(this.Client);
this._kernel = builder.Build();
}
protected override async Task<BedrockAgent> CreateAgentAsync(string agentName)
{
// Create a new agent on the Bedrock Agent service and prepare it for use
var agentModel = await this.Client.CreateAndPrepareAgentAsync(this.GetCreateAgentRequest(agentName));
// Create a new kernel with plugins
Kernel kernel = new();
kernel.Plugins.Add(KernelPluginFactory.CreateFromType<WeatherPlugin>());
// Create a new BedrockAgent instance with the agent model and the client
// so that we can interact with the agent using Semantic Kernel contents.
var bedrockAgent = new BedrockAgent(agentModel, this.Client, this.RuntimeClient)
{
Kernel = kernel,
};
// Create the kernel function action group and prepare the agent for interaction
await bedrockAgent.CreateKernelFunctionActionGroupAsync();
return bedrockAgent;
}
#region private
private readonly Kernel _kernel;
/// <summary>
/// Invoke the agent with the user input.
/// </summary>
private async Task InvokeAgentAsync(Agent agent, string input, bool deleteAgent = true)
{
AgentThread? agentThread = null;
try
{
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(input))
{
agentThread = response.Thread;
WriteAgentChatMessage(response);
}
}
catch (Exception e)
{
Console.WriteLine($"Error invoking agent: {e.Message}");
}
finally
{
if (deleteAgent)
{
var bedrockAgent = agent as BedrockAgent;
await bedrockAgent!.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });
}
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
private sealed class WeatherPlugin
{
[KernelFunction, Description("Provides real-time weather information.")]
public string Current([Description("The location to get the weather for.")] string location)
{
return $"The current weather in {location} is 72 degrees.";
}
[KernelFunction, Description("Forecast weather information.")]
public string Forecast([Description("The location to get the weather for.")] string location)
{
return $"The forecast for {location} is 75 degrees tomorrow.";
}
}
#endregion
}
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.Copilot;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.CopilotStudioAgents;
/// <summary>
/// Demonstrates how to use the <see cref="CopilotStudioAgent"/> to interact with a Copilot Agent service.
/// This sample shows how to create a CopilotStudioAgent, send user messages, and display the agent's responses.
/// </summary>
public sealed class Step01_CopilotStudioAgent(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Fact]
public async Task UseCopilotStudioAgent()
{
CopilotStudioConnectionSettings settings = new(TestConfiguration.GetSection(nameof(CopilotStudioAgent)));
CopilotClient client = CopilotStudioAgent.CreateClient(settings);
CopilotStudioAgent agent = new(client);
await InvokeAgentAsync("Why is the sky blue?");
await InvokeAgentAsync("What is the speed of light?");
// Local function to invoke agent and display the response.
async Task InvokeAgentAsync(string input)
{
Console.WriteLine($"\n# {AuthorRole.User}: {input}");
await foreach (ChatMessageContent response in agent.InvokeAsync(input))
{
WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.Copilot;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.CopilotStudioAgents;
/// <summary>
/// Demonstrates how to use a <see cref="CopilotStudioAgent"/> with a persistent <see cref="CopilotStudioAgentThread"/>
/// to maintain conversation context across multiple user interactions. This sample shows how to send messages to the agent,
/// receive responses, and reset the conversation thread.
/// </summary>
public sealed class Step02_CopilotStudioAgent_Threads(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Fact]
public async Task UseCopilotStudioAgentThread()
{
CopilotStudioConnectionSettings settings = new(TestConfiguration.GetSection(nameof(CopilotStudioAgent)));
CopilotClient client = CopilotStudioAgent.CreateClient(settings);
CopilotStudioAgent agent = new(client);
CopilotStudioAgentThread thread = new(client);
await InvokeAgentAsync("Hello! Who are you? My name is John Doe.");
await InvokeAgentAsync("What is the speed of light?");
await InvokeAgentAsync("What did I just ask?");
await InvokeAgentAsync("What is my name?");
await InvokeAgentAsync("RESET");
await InvokeAgentAsync("Yes");
await InvokeAgentAsync("What is my name?");
// Local function to invoke agent and display the response.
async Task InvokeAgentAsync(string input)
{
Console.WriteLine($"\n# {AuthorRole.User}: {input}");
await foreach (ChatMessageContent response in agent.InvokeAsync(input, thread))
{
WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.CopilotStudio.Client;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.Copilot;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.CopilotStudioAgents;
/// <summary>
/// Demonstrates how to use a Copilot Studio Agent with a persistent conversation thread
/// to perform web search queries and retrieve responses in a .NET test scenario.
/// </summary>
/// <remarks>
/// In Copilot Studio, for the specified agent, you must enable the "Web Search" capability.
/// If not already enabled, make sure to(re-)publish the agent so the changes take effect.
/// </remarks>
public sealed class Step03_CopilotStudioAgent_WebSearch(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Fact]
public async Task UseCopilotStudioAgentThread()
{
CopilotStudioConnectionSettings settings = new(TestConfiguration.GetSection(nameof(CopilotStudioAgent)));
CopilotClient client = CopilotStudioAgent.CreateClient(settings);
CopilotStudioAgent agent = new(client);
CopilotStudioAgentThread thread = new(client);
await InvokeAgentAsync("Which team won the 2025 NCAA Basketball championship?");
await InvokeAgentAsync("What was the final score?");
// Local function to invoke agent and display the response.
async Task InvokeAgentAsync(string input)
{
Console.WriteLine($"\n# {AuthorRole.User}: {input}");
await foreach (ChatMessageContent response in agent.InvokeAsync(input, thread))
{
WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,77 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>GettingStartedWithAgents</AssemblyName>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace></RootNamespace>
<!-- Suppress: "Declare types in namespaces", "Require ConfigureAwait", "Experimental" -->
<NoWarn>$(NoWarn);NU1008;CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0101,SKEXP0110,OPENAI001</NoWarn>
<OutputType>Library</OutputType>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="OpenTelemetry.Exporter.Console" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.abstractions" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<PropertyGroup>
<IncludeAgentUtilities>true</IncludeAgentUtilities>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Agents\A2A\Agents.A2A.csproj" />
<ProjectReference Include="..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\src\Agents\Bedrock\Agents.Bedrock.csproj" />
<ProjectReference Include="..\..\src\Agents\Copilot\Agents.CopilotStudio.csproj" />
<ProjectReference Include="..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\src\Agents\Orchestration\Agents.Orchestration.csproj" />
<ProjectReference Include="..\..\src\Agents\Magentic\Agents.Magentic.csproj" />
<ProjectReference Include="..\..\src\Agents\Runtime\InProcess\Runtime.InProcess.csproj" />
<ProjectReference Include="..\..\src\Agents\Yaml\Agents.Yaml.csproj" />
<ProjectReference Include="..\..\src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj" />
<ProjectReference Include="..\..\src\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" />
<ProjectReference Include="..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
<ProjectReference Include="..\..\src\Functions\Functions.Yaml\Functions.Yaml.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<Using Include="Xunit.Abstractions" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Remove="Resources\*" />
</ItemGroup>
</Project>
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI.Assistants;
using Resources;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// This example demonstrates using <see cref="OpenAIAssistantAgent"/> with templatized instructions.
/// </summary>
public class Step01_Assistant(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseTemplateForAssistantAgent()
{
// Define the agent
string generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml);
// Instructions, Name and Description properties defined via the PromptTemplateConfig.
Assistant definition = await this.AssistantClient.CreateAssistantFromTemplateAsync(this.Model, templateConfig, metadata: SampleMetadata);
OpenAIAssistantAgent agent = new(
definition,
this.AssistantClient,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
{
Arguments = new()
{
{ "topic", "Dog" },
{ "length", "3" }
}
};
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient, metadata: SampleMetadata);
try
{
// Invoke the agent with the default arguments.
await InvokeAgentAsync();
// Invoke the agent with the override arguments.
await InvokeAgentAsync(
new()
{
{ "topic", "Cat" },
{ "length", "3" },
});
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
// Local function to invoke agent and display the response.
async Task InvokeAgentAsync(KernelArguments? arguments = null)
{
await foreach (ChatMessageContent response in agent.InvokeAsync(thread, options: new() { KernelArguments = arguments }))
{
WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
using Plugins;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// Demonstrate creation of <see cref="OpenAIAssistantAgent"/> with a <see cref="KernelPlugin"/>,
/// and then eliciting its response to explicit user messages.
/// </summary>
public class Step02_Assistant_Plugins(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseAssistantWithPlugin()
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAgentAsync(
plugin: KernelPluginFactory.CreateFromType<MenuPlugin>(),
instructions: "Answer questions about the menu.",
name: "Host");
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient);
// Respond to user input
try
{
await InvokeAgentAsync(agent, thread, "Hello");
await InvokeAgentAsync(agent, thread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, thread, "What is the special drink and its price?");
await InvokeAgentAsync(agent, thread, "Thank you");
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
}
[Fact]
public async Task UseAssistantWithPluginEnumParameter()
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAgentAsync(plugin: KernelPluginFactory.CreateFromType<WidgetFactory>());
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient);
// Respond to user input
try
{
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
}
private async Task<OpenAIAssistantAgent> CreateAssistantAgentAsync(KernelPlugin plugin, string? instructions = null, string? name = null)
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name,
instructions: instructions,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient, [plugin]);
return agent;
}
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, AgentThread thread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
using Resources;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// Demonstrate providing image input to <see cref="OpenAIAssistantAgent"/> .
/// </summary>
public class Step03_Assistant_Vision(ITestOutputHelper output) : BaseAssistantTest(output)
{
/// <summary>
/// Azure currently only supports message of type=text.
/// </summary>
protected override bool ForceOpenAI => true;
[Fact]
public async Task UseImageContentWithAssistant()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
// Upload an image
await using Stream imageStream = EmbeddedResource.ReadStream("cat.jpg")!;
string fileId = await this.Client.UploadAssistantFileAsync(imageStream, "cat.jpg");
// Create a thread for the agent conversation.
OpenAIAssistantAgentThread thread = new(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
try
{
// Refer to public image by url
await InvokeAgentAsync(CreateMessageWithImageUrl("Describe this image.", "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/New_york_times_square-terabass.jpg/1200px-New_york_times_square-terabass.jpg"));
await InvokeAgentAsync(CreateMessageWithImageUrl("What are is the main color in this image?", "https://upload.wikimedia.org/wikipedia/commons/5/56/White_shark.jpg"));
// Refer to uploaded image by file-id.
await InvokeAgentAsync(CreateMessageWithImageReference("Is there an animal in this image?", fileId));
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
await this.Client.DeleteFileAsync(fileId);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(ChatMessageContent message)
{
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
private ChatMessageContent CreateMessageWithImageUrl(string input, string url)
=> new(AuthorRole.User, [new TextContent(input), new ImageContent(new Uri(url))]);
private ChatMessageContent CreateMessageWithImageReference(string input, string fileId)
=> new(AuthorRole.User, [new TextContent(input), new FileReferenceContent(fileId)]);
}
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// Demonstrate using code-interpreter on <see cref="OpenAIAssistantAgent"/> .
/// </summary>
public class Step04_AssistantTool_CodeInterpreter(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseCodeInterpreterToolWithAssistantAgent()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
enableCodeInterpreter: true,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
using Resources;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// Demonstrate using <see cref="OpenAIAssistantAgent"/> with file search.
/// </summary>
public class Step05_AssistantTool_FileSearch(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseFileSearchToolWithAssistantAgent()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
enableFileSearch: true,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
// Upload file - Using a table of fictional employees.
await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!;
string fileId = await this.Client.UploadAssistantFileAsync(stream, "employees.pdf");
// Create a vector-store
string vectorStoreId =
await this.Client.CreateVectorStoreAsync(
[fileId],
metadata: SampleMetadata);
// Create a thread associated with a vector-store for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(
this.AssistantClient,
vectorStoreId: vectorStoreId,
metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Who is the youngest employee?");
await InvokeAgentAsync("Who works in sales?");
await InvokeAgentAsync("I have a customer request, who can help me?");
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
await this.Client.DeleteVectorStoreAsync(vectorStoreId);
await this.Client.DeleteFileAsync(fileId);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
using Plugins;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// This example demonstrates how to define function tools for an <see cref="OpenAIAssistantAgent"/>
/// when the assistant is created. This is useful if you want to retrieve the assistant later and
/// then dynamically check what function tools it requires.
/// </summary>
public class Step06_AssistantTool_Function(ITestOutputHelper output) : BaseAssistantTest(output)
{
private const string HostName = "Host";
private const string HostInstructions = "Answer questions about the menu.";
[Fact]
public async Task UseSingleAssistantWithFunctionTools()
{
// Define the agent
AssistantCreationOptions creationOptions =
new()
{
Name = HostName,
Instructions = HostInstructions,
Metadata =
{
{ SampleMetadataKey, bool.TrueString }
},
};
// In this sample the function tools are added to the assistant this is
// important if you want to retrieve the assistant later and then dynamically check
// what function tools it requires.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
plugin.Select(f => f.ToToolDefinition(plugin.Name)).ToList().ForEach(td => creationOptions.Tools.Add(td));
Assistant definition = await this.AssistantClient.CreateAssistantAsync(this.Model, creationOptions);
OpenAIAssistantAgent agent = new(definition, this.AssistantClient);
// Add plugin to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(plugin);
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
try
{
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup and its price?");
await InvokeAgentAsync("What is the special drink and its price?");
await InvokeAgentAsync("Thank you");
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,223 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
namespace GettingStarted.OpenAIAssistants;
/// <summary>
/// This example demonstrates how to declaratively create instances of <see cref="OpenAIAssistantAgent"/>.
/// </summary>
public class Step07_Assistant_Declarative : BaseAssistantTest
{
/// <summary>
/// Demonstrates creating and using a OpenAI Assistant using configuration.
/// </summary>
[Fact]
public async Task OpenAIAssistantAgentWithConfigurationForOpenAI()
{
var text =
"""
type: openai_assistant
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${OpenAI:ChatModelId}
connection:
type: openai
api_key: ${OpenAI:ApiKey}
""";
OpenAIAssistantAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, configuration: TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million");
}
/// <summary>
/// Demonstrates creating and using a OpenAI Assistant using configuration for Azure OpenAI.
/// </summary>
[Fact]
public async Task OpenAIAssistantAgentWithConfigurationForAzureOpenAI()
{
var text =
"""
type: openai_assistant
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${AzureOpenAI:ChatModelId}
connection:
type: azure_openai
endpoint: ${AzureOpenAI:Endpoint}
""";
OpenAIAssistantAgentFactory factory = new();
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<TokenCredential>(new AzureCliCredential());
var kernel = builder.Build();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million");
}
/// <summary>
/// Demonstrates creating and using a OpenAI Assistant using a Kernel.
/// </summary>
[Fact]
public async Task OpenAIAssistantAgentWithKernel()
{
var text =
"""
type: openai_assistant
name: StoryAgent
description: Story Telling Agent
instructions: Tell a story suitable for children about the topic provided by the user.
model:
id: ${AzureOpenAI:ChatModelId}
""";
OpenAIAssistantAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, configuration: TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Cats and Dogs");
}
/// <summary>
/// Demonstrates loading an existing OpenAI Assistant.
/// </summary>
[Fact]
public async Task OpenAIAssistantAgentWithId()
{
var text =
"""
id: ${AzureOpenAI:AgentId}
type: openai_assistant
name: StoryAgent
instructions: Tell a story suitable for children about the topic provided by the user. You always respond in French.
""";
OpenAIAssistantAgentFactory factory = new();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, configuration: TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "Cats and Dogs", deleteAgent: false);
}
/// <summary>
/// Demonstrates creating and using a OpenAI Assistant with templated instructions.
/// </summary>
[Fact]
public async Task OpenAIAssistantAgentWithTemplate()
{
var text =
"""
type: openai_assistant
name: StoryAgent
description: A agent that generates a story about a topic.
instructions: Tell a story about {{$topic}} that is {{$length}} sentences long.
model:
id: ${AzureOpenAI:ChatModelId}
inputs:
topic:
description: The topic of the story.
required: true
default: Cats
length:
description: The number of sentences in the story.
required: true
default: 2
outputs:
output1:
description: output1 description
template:
format: semantic-kernel
""";
OpenAIAssistantAgentFactory factory = new();
var promptTemplateFactory = new KernelPromptTemplateFactory();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel, PromptTemplateFactory = promptTemplateFactory }, TestConfiguration.ConfigurationRoot);
Assert.NotNull(agent);
var options = new AgentInvokeOptions()
{
KernelArguments = new()
{
{ "topic", "Dogs" },
{ "length", "3" },
}
};
AgentThread? agentThread = null;
try
{
await foreach (var response in agent.InvokeAsync(Array.Empty<ChatMessageContent>(), agentThread, options))
{
agentThread = response.Thread;
this.WriteAgentChatMessage(response);
}
}
finally
{
var openaiAgent = agent as OpenAIAssistantAgent;
Assert.NotNull(openaiAgent);
await openaiAgent.Client.DeleteAssistantAsync(openaiAgent.Id);
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
public Step07_Assistant_Declarative(ITestOutputHelper output) : base(output)
{
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<OpenAIClient>(this.Client);
this._kernel = builder.Build();
}
#region private
private readonly Kernel _kernel;
/// <summary>
/// Invoke the agent with the user input.
/// </summary>
private async Task InvokeAgentAsync(Agent agent, string input, bool deleteAgent = true)
{
AgentThread? agentThread = null;
try
{
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, input)))
{
agentThread = response.Thread;
WriteAgentChatMessage(response);
}
}
catch (Exception e)
{
Console.WriteLine($"Error invoking agent: {e.Message}");
}
finally
{
if (deleteAgent)
{
var openaiAgent = (OpenAIAssistantAgent)agent;
await openaiAgent.Client.DeleteAssistantAsync(openaiAgent.Id);
}
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
#endregion
}
@@ -0,0 +1,143 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.OpenAIResponseAgents;
/// <summary>
/// This example demonstrates using <see cref="OpenAIResponseAgent"/>.
/// </summary>
public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponsesAgentTest(output)
{
[Fact]
public async Task UseOpenAIResponseAgentAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in English and French.",
};
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync("What is the capital of France?");
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task UseOpenAIResponseAgentStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in English and French.",
};
// Invoke the agent and output the response
var responseItems = agent.InvokeStreamingAsync("What is the capital of France?");
await WriteAgentStreamMessageAsync(responseItems);
}
[Fact]
public async Task UseOpenAIResponseAgentWithThreadedConversationAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in the users preferred language.",
};
string[] messages =
[
"My name is Bob and my preferred language is French.",
"What is the capital of France?",
"What is the capital of Spain?",
"What is the capital of Italy?"
];
// Initial thread can be null as it will be automatically created
AgentThread? agentThread = null;
// Invoke the agent and output the response
foreach (string message in messages)
{
Console.Write($"Agent Thread Id: {agentThread?.Id}");
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, message), agentThread);
await foreach (AgentResponseItem<ChatMessageContent> responseItem in responseItems)
{
// Update the thread so the previous response id is used
agentThread = responseItem.Thread;
WriteAgentChatMessage(responseItem.Message);
}
}
}
[Fact]
public async Task UseOpenAIResponseAgentWithThreadedConversationStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in the users preferred language.",
};
string[] messages =
[
"My name is Bob and my preferred language is French.",
"What is the capital of France?",
"What is the capital of Spain?",
"What is the capital of Italy?"
];
// Initial thread can be null as it will be automatically created
AgentThread? agentThread = null;
// Invoke the agent and output the response
foreach (string message in messages)
{
Console.Write($"Agent Thread Id: {agentThread?.Id}");
var responseItems = agent.InvokeStreamingAsync(new ChatMessageContent(AuthorRole.User, message), agentThread);
// Update the thread so the previous response id is used
agentThread = await WriteAgentStreamMessageAsync(responseItems);
}
}
[Fact]
public async Task UseOpenAIResponseAgentWithImageContentAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Provide a detailed description including the weather conditions.",
};
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(
AuthorRole.User,
items: [
new TextContent("What is in this image?"),
new ImageContent(new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"))
]
),
];
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(messages);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
}
@@ -0,0 +1,233 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.OpenAIResponseAgents;
/// <summary>
/// This example demonstrates how to manage conversation state during a model interaction using <see cref="OpenAIResponseAgent"/>.
/// OpenAI provides a few ways to manage conversation state, which is important for preserving information across multiple messages or turns in a conversation.
/// See: https://platform.openai.com/docs/guides/conversation-state?api-mode=responses for more information.
/// </summary>
public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper output) : BaseResponsesAgentTest(output)
{
[Fact]
public async Task ManuallyConstructPastConversationAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(AuthorRole.User, "knock knock."),
new ChatMessageContent(AuthorRole.Assistant, "Who's there?"),
new ChatMessageContent(AuthorRole.User, "Orange.")
];
foreach (ChatMessageContent message in messages)
{
WriteAgentChatMessage(message);
}
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(messages);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task ManuallyConstructPastConversationStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(AuthorRole.User, "knock knock."),
new ChatMessageContent(AuthorRole.Assistant, "Who's there?"),
new ChatMessageContent(AuthorRole.User, "Orange.")
];
foreach (ChatMessageContent message in messages)
{
WriteAgentChatMessage(message);
}
// Invoke the agent and output the response
var responseItems = agent.InvokeStreamingAsync(messages);
Console.Write("\n# assistant: ");
await foreach (StreamingChatMessageContent responseItem in responseItems)
{
Console.Write(responseItem.Content);
}
}
[Fact]
public async Task ManageConversationStateWithResponseIdAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
string[] messages =
[
"Tell me a joke?",
"Explain why this is funny?",
];
// Invoke the agent and output the response
AgentThread? agentThread = null;
foreach (string message in messages)
{
var userMessage = new ChatMessageContent(AuthorRole.User, message);
WriteAgentChatMessage(userMessage);
var responseItems = agent.InvokeAsync(userMessage, agentThread);
await foreach (AgentResponseItem<ChatMessageContent> responseItem in responseItems)
{
agentThread = responseItem.Thread;
WriteAgentChatMessage(responseItem.Message);
}
}
}
[Fact]
public async Task ManageConversationStateWithResponseIdStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
string[] messages =
[
"Tell me a joke?",
"Explain why this is funny?",
];
// Invoke the agent and output the response
AgentThread? agentThread = null;
foreach (string message in messages)
{
var userMessage = new ChatMessageContent(AuthorRole.User, message);
WriteAgentChatMessage(userMessage);
Console.Write("\n# assistant: ");
var responseItems = agent.InvokeStreamingAsync(userMessage, agentThread);
await foreach (AgentResponseItem<StreamingChatMessageContent> responseItem in responseItems)
{
agentThread = responseItem.Thread;
Console.Write(responseItem.Message.Content);
}
}
}
[Fact]
public async Task StoreConversationStateAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = true,
};
string[] messages =
[
"Tell me a joke?",
"Explain why this is funny.",
];
// Invoke the agent and output the response
AgentThread? agentThread = null;
foreach (string message in messages)
{
var userMessage = new ChatMessageContent(AuthorRole.User, message);
WriteAgentChatMessage(userMessage);
var responseItems = agent.InvokeAsync(userMessage, agentThread);
await foreach (AgentResponseItem<ChatMessageContent> responseItem in responseItems)
{
agentThread = responseItem.Thread;
WriteAgentChatMessage(responseItem.Message);
}
}
// Display the contents in the latest thread
if (agentThread is not null)
{
this.Output.WriteLine("\n\nResponse Thread Messages\n");
var responseAgentThread = agentThread as OpenAIResponseAgentThread;
var threadMessages = responseAgentThread?.GetMessagesAsync();
if (threadMessages is not null)
{
await foreach (var threadMessage in threadMessages)
{
WriteAgentChatMessage(threadMessage);
}
}
await agentThread.DeleteAsync();
}
}
[Fact]
public async Task StoreConversationStateWithStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = true,
};
string[] messages =
[
"Tell me a joke?",
"Explain why this is funny.",
];
// Invoke the agent and output the response
AgentThread? agentThread = null;
foreach (string message in messages)
{
var userMessage = new ChatMessageContent(AuthorRole.User, message);
WriteAgentChatMessage(userMessage);
Console.Write("\n# assistant: ");
var responseItems = agent.InvokeStreamingAsync(userMessage, agentThread);
await foreach (AgentResponseItem<StreamingChatMessageContent> responseItem in responseItems)
{
agentThread = responseItem.Thread;
Console.Write(responseItem.Message.Content);
}
}
// Display the contents in the latest thread
if (agentThread is not null)
{
this.Output.WriteLine("\n\nResponse Thread Messages\n");
var responseAgentThread = agentThread as OpenAIResponseAgentThread;
var threadMessages = responseAgentThread?.GetMessagesAsync();
if (threadMessages is not null)
{
await foreach (var threadMessage in threadMessages)
{
WriteAgentChatMessage(threadMessage);
}
}
await agentThread.DeleteAsync();
}
}
}
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI.Responses;
using Plugins;
namespace GettingStarted.OpenAIResponseAgents;
/// <summary>
/// This example demonstrates using <see cref="OpenAIResponseAgent"/>.
/// </summary>
public class Step03_OpenAIResponseAgent_ReasoningModel(ITestOutputHelper output) : BaseResponsesAgentTest(output, "o4-mini")
{
[Fact]
public async Task UseOpenAIResponseAgentWithAReasoningModelAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries with a detailed response.",
};
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync("Which of the last four Olympic host cities has the highest average temperature?");
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task UseOpenAIResponseAgentWithAReasoningModelAndSummariesAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId);
// ResponseCreationOptions allows you to specify tools for the agent.
OpenAIResponseAgentInvokeOptions invokeOptions = new()
{
ResponseCreationOptions = new()
{
ReasoningOptions = new()
{
ReasoningEffortLevel = ResponseReasoningEffortLevel.High,
// This parameter cannot be used due to a known issue in the OpenAI .NET SDK.
// https://github.com/openai/openai-dotnet/issues/457
// ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed,
},
},
};
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(
"""
Instructions:
- Given the React component below, change it so that nonfiction books have red
text.
- Return only the code in your reply
- Do not include any additional formatting, such as markdown code blocks
- For formatting, use four space tabs, and do not allow any lines of code to
exceed 80 columns
const books = [
{ title: 'Dune', category: 'fiction', id: 1 },
{ title: 'Frankenstein', category: 'fiction', id: 2 },
{ title: 'Moneyball', category: 'nonfiction', id: 3 },
];
export default function BookList() {
const listItems = books.map(book =>
<li>
{book.title}
</li>
);
return (
<ul>{listItems}</ul>
);
}
""", options: invokeOptions);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task UseOpenAIResponseAgentWithAReasoningModelAndToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries with a detailed response.",
};
// Create a plugin that defines the tools to be used by the agent.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync("What is the best value healthy meal?");
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
}
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using System.ClientModel.Primitives;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Files;
using OpenAI.Responses;
using OpenAI.VectorStores;
using Plugins;
using Resources;
namespace GettingStarted.OpenAIResponseAgents;
/// <summary>
/// This example demonstrates how to use tools during a model interaction using <see cref="OpenAIResponseAgent"/>.
/// </summary>
public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseResponsesAgentTest(output)
{
[Fact]
public async Task InvokeAgentWithFunctionToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
// Create a plugin that defines the tools to be used by the agent.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(AuthorRole.User, "What is the special soup and its price?"),
new ChatMessageContent(AuthorRole.User, "What is the special drink and its price?"),
];
foreach (ChatMessageContent message in messages)
{
WriteAgentChatMessage(message);
}
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(messages);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task InvokeAgentWithWebSearchAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
// ResponseCreationOptions allows you to specify tools for the agent.
CreateResponseOptions creationOptions = new();
creationOptions.Tools.Add(ResponseTool.CreateWebSearchTool());
OpenAIResponseAgentInvokeOptions invokeOptions = new()
{
ResponseCreationOptions = creationOptions,
};
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync("What was a positive news story from today?", options: invokeOptions);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task InvokeAgentWithFileSearchAsync()
{
// Upload a file to the OpenAI File API
await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!;
OpenAIFile file = await this.FileClient.UploadFileAsync(stream, filename: "employees.pdf", purpose: FileUploadPurpose.UserData);
// Create a vector store for the file
ClientResult<VectorStore> createStoreOp = await this.VectorStoreClient.CreateVectorStoreAsync(
new VectorStoreCreationOptions()
{
FileIds = { file.Id },
});
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
// ResponseCreationOptions allows you to specify tools for the agent.
CreateResponseOptions creationOptions = new();
creationOptions.Tools.Add(ResponseTool.CreateFileSearchTool([createStoreOp.Value.Id], null));
OpenAIResponseAgentInvokeOptions invokeOptions = new()
{
ResponseCreationOptions = creationOptions,
};
// Invoke the agent and output the response
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(AuthorRole.User, "Who is the youngest employee?"),
new ChatMessageContent(AuthorRole.User, "Who works in sales?"),
new ChatMessageContent(AuthorRole.User, "I have a customer request, who can help me?"),
];
foreach (ChatMessageContent message in messages)
{
WriteAgentChatMessage(message);
}
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(messages, options: invokeOptions);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
// Clean up resources
RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow };
this.FileClient.DeleteFile(file.Id, noThrowOptions);
this.VectorStoreClient.DeleteVectorStore(createStoreOp.Value.Id, noThrowOptions);
}
[Fact]
public async Task InvokeAgentWithMultipleToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
// Create a plugin that defines the tools to be used by the agent.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
ICollection<ChatMessageContent> messages =
[
new ChatMessageContent(AuthorRole.User, "What is the special soup and its price?"),
new ChatMessageContent(AuthorRole.User, "What is the special drink and its price?"),
];
foreach (ChatMessageContent message in messages)
{
WriteAgentChatMessage(message);
}
// ResponseCreationOptions allows you to specify tools for the agent.
CreateResponseOptions creationOptions = new();
creationOptions.Tools.Add(ResponseTool.CreateWebSearchTool());
OpenAIResponseAgentInvokeOptions invokeOptions = new()
{
ResponseCreationOptions = creationOptions,
};
// Invoke the agent and output the response
var responseItems = agent.InvokeAsync(messages, options: invokeOptions);
await foreach (ChatMessageContent responseItem in responseItems)
{
WriteAgentChatMessage(responseItem);
}
}
}
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/>
/// for executing multiple agents on the same task in parallel.
/// </summary>
public class Step01_Concurrent(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task ConcurrentTaskAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent physicist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in physics. You answer questions from a physics perspective.",
name: "Physicist",
description: "An expert in physics");
ChatCompletionAgent chemist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
name: "Chemist",
description: "An expert in chemistry");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
ConcurrentOrchestration orchestration =
new(physicist, chemist)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "What is temperature?";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string[]> result = await orchestration.InvokeAsync(input, runtime);
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
}
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Resources;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="ConcurrentOrchestration"/> with structured output.
/// </summary>
public class Step01a_ConcurrentWithStructuredOutput(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
[Fact]
public async Task ConcurrentStructuredOutputAsync()
{
// Define the agents
ChatCompletionAgent agent1 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in identifying themes in articles. Given an article, identify the main themes.",
description: "An expert in identifying themes in articles");
ChatCompletionAgent agent2 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in sentiment analysis. Given an article, identify the sentiment.",
description: "An expert in sentiment analysis");
ChatCompletionAgent agent3 =
this.CreateChatCompletionAgent(
instructions: "You are an expert in entity recognition. Given an article, extract the entities.",
description: "An expert in entity recognition");
// Define the orchestration with transform
Kernel kernel = this.CreateKernelWithChatCompletion();
StructuredOutputTransform<Analysis> outputTransform =
new(kernel.GetRequiredService<IChatCompletionService>(),
new OpenAIPromptExecutionSettings { ResponseFormat = typeof(Analysis) });
ConcurrentOrchestration<string, Analysis> orchestration =
new(agent1, agent2, agent3)
{
LoggerFactory = this.LoggerFactory,
ResultTransform = outputTransform.TransformAsync,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
const string resourceId = "Hamlet_full_play_summary.txt";
string input = EmbeddedResource.Read(resourceId);
Console.WriteLine($"\n# INPUT: @{resourceId}\n");
OrchestrationResult<Analysis> result = await orchestration.InvokeAsync(input, runtime);
Analysis output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
Console.WriteLine($"\n# RESULT:\n{JsonSerializer.Serialize(output, s_options)}");
await runtime.RunUntilIdleAsync();
}
private sealed class Analysis
{
public IList<string> Themes { get; set; } = [];
public IList<string> Sentiments { get; set; } = [];
public IList<string> Entities { get; set; } = [];
}
}
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="SequentialOrchestration"/> for
/// executing multiple agents in sequence, i.e.the output of one agent is
/// the input to the next agent.
/// </summary>
public class Step02_Sequential(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SequentialTaskAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent analystAgent =
this.CreateChatCompletionAgent(
name: "Analyst",
instructions:
"""
You are a marketing analyst. Given a product description, identify:
- Key features
- Target audience
- Unique selling points
""",
description: "A agent that extracts key concepts from a product description.");
ChatCompletionAgent writerAgent =
this.CreateChatCompletionAgent(
name: "copywriter",
instructions:
"""
You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
compose a compelling marketing copy (like a newsletter section) that highlights these points.
Output should be short (around 150 words), output just the copy as a single text block.
""",
description: "An agent that writes a marketing copy based on the extracted concepts.");
ChatCompletionAgent editorAgent =
this.CreateChatCompletionAgent(
name: "editor",
instructions:
"""
You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
give format and make it polished. Output the final improved copy as a single text block.
""",
description: "An agent that formats and proofreads the marketing copy.");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
SequentialOrchestration orchestration =
new(analystAgent, writerAgent, editorAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
}
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use cancel a <see cref="SequentialOrchestration"/> while its running.
/// </summary>
public class Step02a_SequentialCancellation(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task SequentialCancelledAsync()
{
// Define the agents
ChatCompletionAgent agent =
this.CreateChatCompletionAgent(
"""
If the input message is a number, return the number incremented by one.
""",
description: "A agent that increments numbers.");
// Define the orchestration
SequentialOrchestration orchestration = new(agent) { LoggerFactory = this.LoggerFactory };
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "42";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
result.Cancel();
await Task.Delay(TimeSpan.FromSeconds(3));
try
{
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
}
catch
{
Console.WriteLine("\n# CANCELLED");
}
await runtime.RunUntilIdleAsync();
}
}
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/> ith a default
/// round robin manager for controlling the flow of conversation in a round robin fashion.
/// </summary>
/// <remarks>
/// Think of the group chat manager as a state machine, with the following possible states:
/// - Request for user message
/// - Termination, after which the manager will try to filter a result from the conversation
/// - Continuation, at which the manager will select the next agent to speak.
/// </remarks>
public class Step03_GroupChat(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task GroupChatAsync(bool streamedResponse)
{
// Define the agents
ChatCompletionAgent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
ChatCompletionAgent editor =
this.CreateChatCompletionAgent(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
GroupChatOrchestration orchestration =
new(new AuthorCriticManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input = "Create a slogan for a new electric SUV that is affordable and fun to drive.";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class AuthorCriticManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The reviewer has approved the copy." };
}
}
return result;
}
}
}
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/> with human in the loop
/// </summary>
public class Step03a_GroupChatWithHumanInTheLoop(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task GroupChatWithHumanAsync()
{
// Define the agents
ChatCompletionAgent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
ChatCompletionAgent editor =
this.CreateChatCompletionAgent(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
bool didUserRespond = false;
GroupChatOrchestration orchestration =
new(
new HumanInTheLoopChatManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5,
InteractiveCallback = () =>
{
// Simlulate user input that first replies "No" and then "Yes"
ChatMessageContent input = new(AuthorRole.User, didUserRespond ? "Yes" : "More pizzazz");
didUserRespond = true;
Console.WriteLine($"\n# INPUT: {input.Content}\n");
return ValueTask.FromResult(input);
}
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "Create a slogan for a new electric SUV that is affordable and fun to drive.";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
}
/// <summary>
/// Define a custom group chat manager that enables user input.
/// </summary>
/// <remarks>
/// User input is achieved by overriding the default round robin manager
/// to allow user input after the reviewer agent's message.
/// </remarks>
private sealed class HumanInTheLoopChatManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.Role == AuthorRole.User && $"{lastMessage}".Contains("Yes", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The user is satisfied with the copy." };
}
}
return result;
}
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is null)
{
return ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "No agents have spoken yet." });
}
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
return ValueTask.FromResult(new GroupChatManagerResult<bool>(true) { Reason = "User input is needed after the reviewer's message." });
}
return ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "User input is not needed until the reviewer's message." });
}
}
}
@@ -0,0 +1,214 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="GroupChatOrchestration"/>
/// with a group chat manager that uses a chat completion service to
/// control the flow of the conversation.
/// </summary>
public class Step03b_GroupChatWithAIManager(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task GroupChatWithAIManagerAsync()
{
// Define the agents
ChatCompletionAgent farmer =
this.CreateChatCompletionAgent(
name: "Farmer",
description: "A rural farmer from Southeast Asia.",
instructions:
"""
You're a farmer from Southeast Asia.
Your life is deeply connected to land and family.
You value tradition and sustainability.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent developer =
this.CreateChatCompletionAgent(
name: "Developer",
description: "An urban software developer from the United States.",
instructions:
"""
You're a software developer from the United States.
Your life is fast-paced and technology-driven.
You value innovation, freedom, and work-life balance.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent teacher =
this.CreateChatCompletionAgent(
name: "Teacher",
description: "A retired history teacher from Eastern Europe",
instructions:
"""
You're a retired history teacher from Eastern Europe.
You bring historical and philosophical perspectives to discussions.
You value legacy, learning, and cultural continuity.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent activist =
this.CreateChatCompletionAgent(
name: "Activist",
description: "A young activist from South America.",
instructions:
"""
You're a young activist from South America.
You focus on social justice, environmental rights, and generational change.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent spiritual =
this.CreateChatCompletionAgent(
name: "SpiritualLeader",
description: "A spiritual leader from the Middle East.",
instructions:
"""
You're a spiritual leader from the Middle East.
You provide insights grounded in religion, morality, and community service.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent artist =
this.CreateChatCompletionAgent(
name: "Artist",
description: "An artist from Africa.",
instructions:
"""
You're an artist from Africa.
You view life through creative expression, storytelling, and collective memory.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent immigrant =
this.CreateChatCompletionAgent(
name: "Immigrant",
description: "An immigrant entrepreneur from Asia living in Canada.",
instructions:
"""
You're an immigrant entrepreneur from Asia living in Canada.
You balance trandition with adaption.
You focus on family success, risk, and opportunity.
You are in a debate. Feel free to challenge the other participants with respect.
""");
ChatCompletionAgent doctor =
this.CreateChatCompletionAgent(
name: "Doctor",
description: "A doctor from Scandinavia.",
instructions:
"""
You're a doctor from Scandinavia.
Your perspective is shaped by public health, equity, and structured societal support.
You are in a debate. Feel free to challenge the other participants with respect.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
const string topic = "What does a good life mean to you personally?";
Kernel kernel = this.CreateKernelWithChatCompletion();
GroupChatOrchestration orchestration =
new(
new AIGroupChatManager(
topic,
kernel.GetRequiredService<IChatCompletionService>())
{
MaximumInvocationCount = 5
},
farmer,
developer,
teacher,
activist,
spiritual,
artist,
immigrant,
doctor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT: {topic}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(topic, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
}
private sealed class AIGroupChatManager(string topic, IChatCompletionService chatCompletion) : GroupChatManager
{
private static class Prompts
{
public static string Termination(string topic) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You need to determine if the discussion has reached a conclusion.
If you would like to end the discussion, please respond with True. Otherwise, respond with False.
""";
public static string Selection(string topic, string participants) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You need to select the next participant to speak.
Here are the names and descriptions of the participants:
{participants}\n
Please respond with only the name of the participant you would like to select.
""";
public static string Filter(string topic) =>
$"""
You are mediator that guides a discussion on the topic of '{topic}'.
You have just concluded the discussion.
Please summarize the discussion and provide a closing statement.
""";
}
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default) =>
this.GetResponseAsync<string>(history, Prompts.Filter(topic), cancellationToken);
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<string>> SelectNextAgent(ChatHistory history, GroupChatTeam team, CancellationToken cancellationToken = default) =>
this.GetResponseAsync<string>(history, Prompts.Selection(topic, team.FormatList()), cancellationToken);
/// <inheritdoc/>
public override ValueTask<GroupChatManagerResult<bool>> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default) =>
ValueTask.FromResult(new GroupChatManagerResult<bool>(false) { Reason = "The AI group chat manager does not request user input." });
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
result = await this.GetResponseAsync<bool>(history, Prompts.Termination(topic), cancellationToken);
}
return result;
}
private async ValueTask<GroupChatManagerResult<TValue>> GetResponseAsync<TValue>(ChatHistory history, string prompt, CancellationToken cancellationToken = default)
{
OpenAIPromptExecutionSettings executionSettings = new() { ResponseFormat = typeof(GroupChatManagerResult<TValue>) };
ChatHistory request = [.. history, new ChatMessageContent(AuthorRole.System, prompt)];
ChatMessageContent response = await chatCompletion.GetChatMessageContentAsync(request, executionSettings, kernel: null, cancellationToken);
string responseText = response.ToString();
return
JsonSerializer.Deserialize<GroupChatManagerResult<TValue>>(responseText) ??
throw new InvalidOperationException($"Failed to parse response: {responseText}");
}
}
}
@@ -0,0 +1,123 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="HandoffOrchestration"/> that represents
/// a customer support triage system.The orchestration consists of 4 agents, each specialized
/// in a different area of customer support: triage, refunds, order status, and order returns.
/// </summary>
public class Step04_Handoff(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task OrderSupportAsync(bool streamedResponse)
{
// Define the agents & tools
ChatCompletionAgent triageAgent =
this.CreateChatCompletionAgent(
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
ChatCompletionAgent statusAgent =
this.CreateChatCompletionAgent(
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.");
statusAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderStatusPlugin()));
ChatCompletionAgent returnAgent =
this.CreateChatCompletionAgent(
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.");
returnAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderReturnPlugin()));
ChatCompletionAgent refundAgent =
this.CreateChatCompletionAgent(
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.");
refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define user responses for InteractiveCallback (since sample is not interactive)
Queue<string> responses = new();
string task = "I am a customer that needs help with my orders";
responses.Enqueue("I'd like to track the status of my order");
responses.Enqueue("My order ID is 123");
responses.Enqueue("I want to return another order of mine");
responses.Enqueue("Order ID 321");
responses.Enqueue("Broken item");
responses.Enqueue("No, bye");
// Define the orchestration
HandoffOrchestration orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, statusAgent, returnAgent, refundAgent)
.Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
triageAgent,
statusAgent,
returnAgent,
refundAgent)
{
InteractiveCallback = () =>
{
string input = responses.Dequeue();
Console.WriteLine($"\n# INPUT: {input}\n");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{task}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(300));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class OrderStatusPlugin
{
[KernelFunction]
public string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
}
private sealed class OrderReturnPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
}
private sealed class OrderRefundPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
}
}
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="HandoffOrchestration"/>.
/// </summary>
public class Step04a_HandoffWithStructuredInput(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task HandoffStructuredInputAsync()
{
// Initialize plugin
GithubPlugin githubPlugin = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromObject(githubPlugin);
// Define the agents
ChatCompletionAgent triageAgent =
this.CreateChatCompletionAgent(
instructions: "Given a GitHub issue, triage it.",
name: "TriageAgent",
description: "An agent that triages GitHub issues");
ChatCompletionAgent pythonAgent =
this.CreateChatCompletionAgent(
instructions: "You are an agent that handles Python related GitHub issues.",
name: "PythonAgent",
description: "An agent that handles Python related issues");
pythonAgent.Kernel.Plugins.Add(plugin);
ChatCompletionAgent dotnetAgent =
this.CreateChatCompletionAgent(
instructions: "You are an agent that handles .NET related GitHub issues.",
name: "DotNetAgent",
description: "An agent that handles .NET related issues");
dotnetAgent.Kernel.Plugins.Add(plugin);
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
HandoffOrchestration<GithubIssue, string> orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, dotnetAgent, pythonAgent),
triageAgent,
pythonAgent,
dotnetAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
GithubIssue input =
new()
{
Id = "12345",
Title = "Bug: SQLite Error 1: 'ambiguous column name:' when including VectorStoreRecordKey in VectorSearchOptions.Filter",
Body =
"""
Describe the bug
When using column names marked as [VectorStoreRecordData(IsFilterable = true)] in VectorSearchOptions.Filter, the query runs correctly.
However, using the column name marked as [VectorStoreRecordKey] in VectorSearchOptions.Filter, the query throws exception 'SQLite Error 1: ambiguous column name: StartUTC'.
To Reproduce
Add a filter for the column marked [VectorStoreRecordKey]. Since that same column exists in both the vec_TestTable and TestTable, the data for both columns cannot be returned.
Expected behavior
The query should explicitly list the vec_TestTable column names to retrieve and should omit the [VectorStoreRecordKey] column since it will be included in the primary TestTable columns.
Platform
Microsoft.SemanticKernel.Connectors.Sqlite v1.46.0-preview
Additional context
Normal DBContext logging shows only normal context queries. Queries run by VectorizedSearchAsync() don't appear in those logs and I could not find a way to enable logging in semantic search so that I could actually see the exact query that is failing. It would have been very useful to see the failing semantic query.
""",
Labels = []
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{input.Id}: {input.Title}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT: {text}");
Console.WriteLine($"\n# LABELS: {string.Join(",", githubPlugin.Labels["12345"])}\n");
await runtime.RunUntilIdleAsync();
}
private sealed class GithubIssue
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
[JsonPropertyName("body")]
public string Body { get; set; } = string.Empty;
[JsonPropertyName("labels")]
public string[] Labels { get; set; } = [];
}
private sealed class GithubPlugin
{
public Dictionary<string, string[]> Labels { get; } = [];
[KernelFunction]
public void AddLabels(string issueId, params string[] labels)
{
this.Labels[issueId] = labels;
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.Magentic;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="MagenticOrchestration"/> with two agents:
/// - A Research agent that can perform web searches
/// - A Coder agent that can run code using the code interpreter
/// </summary>
public class Step05_Magentic(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
private const string ManagerModel = "o3-mini";
private const string ResearcherModel = "gpt-4o-search-preview";
/// <summary>
/// Require OpenAI services in order to use "gpt-4o-search-preview" model
/// </summary>
protected override bool ForceOpenAI => true;
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task MagenticTaskAsync(bool streamedResponse)
{
// Define the agents
Kernel researchKernel = CreateKernelWithOpenAIChatCompletion(ResearcherModel);
ChatCompletionAgent researchAgent =
this.CreateChatCompletionAgent(
name: "ResearchAgent",
description: "A helpful assistant with access to web search. Ask it to perform web searches.",
instructions: "You are a Researcher. You find information without additional computation or quantitative analysis.",
kernel: researchKernel);
PersistentAgentsClient agentsClient = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
PersistentAgent definition =
await agentsClient.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
name: "CoderAgent",
description: "Write and executes code to process and analyze data.",
instructions: "You solve questions using code. Please provide detailed analysis and computation process.",
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent coderAgent = new(definition, agentsClient);
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
Kernel managerKernel = this.CreateKernelWithChatCompletion(ManagerModel);
StandardMagenticManager manager =
new(managerKernel.GetRequiredService<IChatCompletionService>(), new OpenAIPromptExecutionSettings())
{
MaximumInvocationCount = 5,
};
MagenticOrchestration orchestration =
new(manager, researchAgent, coderAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
StreamingResponseCallback = streamedResponse ? monitor.StreamingResultCallback : null,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input =
"""
I am preparing a report on the energy efficiency of different machine learning model architectures.
Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 on standard datasets
(e.g., ImageNet for ResNet, GLUE for BERT, WebText for GPT-2).
Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 VM for 24 hours.
Provide tables for clarity, and recommend the most energy-efficient model per task type
(image classification, text classification, and text generation).
""";
Console.WriteLine($"\n# INPUT:\n{input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 20));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private Kernel CreateKernelWithOpenAIChatCompletion(string model)
{
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
model,
TestConfiguration.OpenAI.ApiKey);
return builder.Build();
}
}
@@ -0,0 +1,332 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Magentic;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted.Orchestration;
/// <summary>
/// Demonstrates how to use the <see cref="MagenticOrchestration"/> with two agents:
/// - A Research agent that can perform web searches
/// - A Coder agent that can run code using the code interpreter
/// </summary>
public class Step06_DifferentAgentTypes(ITestOutputHelper output) : BaseOrchestrationTest(output)
{
[Fact]
public async Task ConcurrentOrchestrationAsync()
{
// Define the agents
Agent physicist =
this.CreateChatCompletionAgent(
instructions: "You are an expert in physics. You answer questions from a physics perspective.",
name: "Physicist",
description: "An expert in physics");
Agent chemist =
await this.CreateAzureAIAgentAsync(
instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
name: "Chemist",
description: "An expert in chemistry");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
ConcurrentOrchestration orchestration =
new(physicist, chemist)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "What is temperature?";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string[]> result = await orchestration.InvokeAsync(input, runtime);
string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task SequentialOrchestrationAsync()
{
// Define the agents
Agent analystAgent =
this.CreateChatCompletionAgent(
name: "Analyst",
instructions:
"""
You are a marketing analyst. Given a product description, identify:
- Key features
- Target audience
- Unique selling points
""",
description: "A agent that extracts key concepts from a product description.");
Agent writerAgent =
await this.CreateOpenAIAssistantAgentAsync(
name: "copywriter",
instructions:
"""
You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
compose a compelling marketing copy (like a newsletter section) that highlights these points.
Output should be short (around 150 words), output just the copy as a single text block.
""",
description: "An agent that writes a marketing copy based on the extracted concepts.");
Agent editorAgent =
await this.CreateAzureAIAgentAsync(
name: "editor",
instructions:
"""
You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
give format and make it polished. Output the final improved copy as a single text block.
""",
description: "An agent that formats and proofreads the marketing copy.");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
SequentialOrchestration orchestration =
new(analystAgent, writerAgent, editorAgent)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task GroupChatOrchestrationAsync()
{
// Define the agents
Agent writer =
this.CreateChatCompletionAgent(
name: "CopyWriter",
description: "A copy writer",
instructions:
"""
You are a copywriter with ten years of experience and are known for brevity and a dry humor.
The goal is to refine and decide on the single best copy as an expert in the field.
Only provide a single proposal per response.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""");
Agent editor =
await this.CreateOpenAIAssistantAgentAsync(
name: "Reviewer",
description: "An editor.",
instructions:
"""
You are an art director who has opinions about copywriting born of a love for David Ogilvy.
The goal is to determine if the given copy is acceptable to print.
If so, state: "I Approve".
If not, provide insight on how to refine suggested copy without example.
""");
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define the orchestration
GroupChatOrchestration orchestration =
new(new AuthorCriticManager(writer.Name!, editor.Name!)
{
MaximumInvocationCount = 5
},
writer,
editor)
{
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
string input = "Create a slogan for a new electric SUV that is affordable and fun to drive.";
Console.WriteLine($"\n# INPUT: {input}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
[Fact]
public async Task HandoffOrchestrationAsync()
{
// Define the agents & tools
Agent triageAgent =
this.CreateChatCompletionAgent(
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
Agent statusAgent =
this.CreateChatCompletionAgent(
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.");
statusAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderStatusPlugin()));
Agent returnAgent =
this.CreateChatCompletionAgent(
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.");
returnAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderReturnPlugin()));
Agent refundAgent =
this.CreateChatCompletionAgent(
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.");
refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
// Create a monitor to capturing agent responses (via ResponseCallback)
// to display at the end of this sample. (optional)
// NOTE: Create your own callback to capture responses in your application or service.
OrchestrationMonitor monitor = new();
// Define user responses for InteractiveCallback (since sample is not interactive)
Queue<string> responses = new();
string task = "I am a customer that needs help with my orders";
responses.Enqueue("I'd like to track the status of my order");
responses.Enqueue("My order ID is 123");
responses.Enqueue("I want to return another order of mine");
responses.Enqueue("Order ID 321");
responses.Enqueue("Broken item");
responses.Enqueue("No, bye");
// Define the orchestration
HandoffOrchestration orchestration =
new(OrchestrationHandoffs
.StartWith(triageAgent)
.Add(triageAgent, statusAgent, returnAgent, refundAgent)
.Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
triageAgent,
statusAgent,
returnAgent,
refundAgent)
{
InteractiveCallback = () =>
{
string input = responses.Dequeue();
Console.WriteLine($"\n# INPUT: {input}\n");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
LoggerFactory = this.LoggerFactory,
ResponseCallback = monitor.ResponseCallback,
};
// Start the runtime
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
Console.WriteLine($"\n# INPUT:\n{task}\n");
OrchestrationResult<string> result = await orchestration.InvokeAsync(task, runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 10));
Console.WriteLine($"\n# RESULT: {text}");
await runtime.RunUntilIdleAsync();
Console.WriteLine("\n\nORCHESTRATION HISTORY");
foreach (ChatMessageContent message in monitor.History)
{
this.WriteAgentChatMessage(message);
}
}
private sealed class OrderStatusPlugin
{
[KernelFunction]
public string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
}
private sealed class OrderReturnPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
}
private sealed class OrderRefundPlugin
{
[KernelFunction]
public string ProcessReturn(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
}
private sealed class AuthorCriticManager(string authorName, string criticName) : RoundRobinGroupChatManager
{
public override ValueTask<GroupChatManagerResult<string>> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
{
ChatMessageContent finalResult = history.Last(message => message.AuthorName == authorName);
return ValueTask.FromResult(new GroupChatManagerResult<string>($"{finalResult}") { Reason = "The approved copy." });
}
/// <inheritdoc/>
public override async ValueTask<GroupChatManagerResult<bool>> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
{
// Has the maximum invocation count been reached?
GroupChatManagerResult<bool> result = await base.ShouldTerminate(history, cancellationToken);
if (!result.Value)
{
// If not, check if the reviewer has approved the copy.
ChatMessageContent? lastMessage = history.LastOrDefault();
if (lastMessage is not null && lastMessage.AuthorName == criticName && $"{lastMessage}".Contains("I Approve", StringComparison.OrdinalIgnoreCase))
{
// If the reviewer approves, we terminate the chat.
result = new GroupChatManagerResult<bool>(true) { Reason = "The reviewer has approved the copy." };
}
}
return result;
}
}
}
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
namespace Plugins;
public sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
public MenuItem[] GetMenu()
{
return s_menuItems;
}
[KernelFunction, Description("Provides a list of specials from the menu.")]
public MenuItem[] GetSpecials()
{
return [.. s_menuItems.Where(i => i.IsSpecial)];
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public float? GetItemPrice(
[Description("The name of the menu item.")]
string menuItem)
{
return s_menuItems.FirstOrDefault(i => i.Name.Equals(menuItem, StringComparison.OrdinalIgnoreCase))?.Price;
}
private static readonly MenuItem[] s_menuItems =
[
new()
{
Category = "Soup",
Name = "Clam Chowder",
Price = 4.95f,
IsSpecial = true,
},
new()
{
Category = "Soup",
Name = "Tomato Soup",
Price = 4.95f,
IsSpecial = false,
},
new()
{
Category = "Salad",
Name = "Cobb Salad",
Price = 9.99f,
},
new()
{
Category = "Salad",
Name = "House Salad",
Price = 4.95f,
},
new()
{
Category = "Drink",
Name = "Chai Tea",
Price = 2.95f,
IsSpecial = true,
},
new()
{
Category = "Drink",
Name = "Soda",
Price = 1.95f,
},
];
public sealed class MenuItem
{
public string Category { get; init; }
public string Name { get; init; }
public float Price { get; init; }
public bool IsSpecial { get; init; }
}
}
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel;
namespace Plugins;
/// <summary>
/// A plugin that creates widgets.
/// </summary>
public sealed class WidgetFactory
{
[KernelFunction]
[Description("Creates a new widget of the specified type and colors")]
public WidgetDetails CreateWidget(
[Description("The type of widget to be created")] WidgetType widgetType,
[Description("The colors of the widget to be created")] WidgetColor[] widgetColors)
{
return new()
{
SerialNumber = $"{widgetType}-{string.Join("-", widgetColors)}-{Guid.NewGuid()}",
Type = widgetType,
Colors = widgetColors,
};
}
}
/// <summary>
/// A <see cref="JsonConverter"/> is required to correctly convert enum values.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WidgetType
{
[Description("A widget that is useful.")]
Useful,
[Description("A widget that is decorative.")]
Decorative
}
/// <summary>
/// A <see cref="JsonConverter"/> is required to correctly convert enum values.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WidgetColor
{
[Description("Use when creating a red item.")]
Red,
[Description("Use when creating a green item.")]
Green,
[Description("Use when creating a blue item.")]
Blue
}
public sealed class WidgetDetails
{
public string SerialNumber { get; init; }
public WidgetType Type { get; init; }
public WidgetColor[] Colors { get; init; }
}
@@ -0,0 +1,165 @@
# Semantic Kernel Agents - Getting Started
This project contains a step by step guide to get started with _Semantic Kernel Agents_.
## NuGet
- [Microsoft.SemanticKernel.Agents.Abstractions](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.Abstractions)
- [Microsoft.SemanticKernel.Agents.Core](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.Core)
- [Microsoft.SemanticKernel.Agents.OpenAI](https://www.nuget.org/packages/Microsoft.SemanticKernel.Agents.OpenAI)
## Source
- [Semantic Kernel Agent Framework](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/Agents)
The examples can be run as integration tests but their code can also be copied to stand-alone programs.
## Examples
The getting started with agents examples include:
### ChatCompletion
Example|Description
---|---
[Step01_Agent](./dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs)|How to create and use an agent.
[Step02_Plugins](./dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs)|How to associate plug-ins with an agent.
[Step03_Chat](./dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs)|How to create a conversation between agents.
[Step04_KernelFunctionStrategies](./dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_.
[Step05_JsonResult](./dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs)|How to have an agent produce JSON.
[Step06_DependencyInjection](./dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs)|How to define dependency injection patterns for agents.
[Step07_Telemetry](./dotnet/samples/GettingStartedWithAgents/Step07_Telemetry.cs)|How to enable logging for agents.
### Open AI Assistant
Example|Description
---|---
[Step01_Assistant](./dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step01_Assistant.cs)|How to create an Open AI Assistant agent.
[Step02_Assistant_Plugins](./dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step02_Assistant_Plugins.cs)|How to associate plug-ins with an Open AI Assistant agent.
[Step03_Assistant_Vision](./dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step03_Assistant_Vision.cs)|How to provide an image as input to an Open AI Assistant agent.
[Step04_AssistantTool_CodeInterpreter_](./dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step04_AssistantTool_CodeInterpreter.cs)|How to use the code-interpreter tool for an Open AI Assistant agent.
[Step05_AssistantTool_FileSearch](./dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step05_AssistantTool_FileSearch.cs)|How to use the file-search tool for an Open AI Assistant agent.
### Azure AI Agent
Example|Description
---|---
[Step01_AzureAIAgent](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step01_AzureAIAgent.cs)|How to create an `AzureAIAgent`.
[Step02_AzureAIAgent_Plugins](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step02_AzureAIAgent_Plugins.cs)|How to associate plug-ins with an `AzureAIAgent`.
[Step03_AzureAIAgent_Chat](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step03_AzureAIAgent_Chat.cs)|How create a conversation with `AzureAIAgent`s.
[Step04_AzureAIAgent_CodeInterpreter](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step04_AzureAIAgent_CodeInterpreter.cs)|How to use the code-interpreter tool for an `AzureAIAgent`.
[Step05_AzureAIAgent_FileSearch](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step05_AzureAIAgent_FileSearch.cs)|How to use the file-search tool for an `AzureAIAgent`.
[Step06_AzureAIAgent_OpenAPI](./dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step06_AzureAIAgent_OpenAPI.cs)|How to use the Open API tool for an `AzureAIAgent`.
### Bedrock Agent
Example|Description
---|---
[Step01_BedrockAgent](./BedrockAgent/Step01_BedrockAgent.cs)|How to create a `BedrockAgent` and interact with it in the most basic way.
[Step02_BedrockAgent_CodeInterpreter](./BedrockAgent/Step02_BedrockAgent_CodeInterpreter.cs)|How to use the code-interpreter tool with a `BedrockAgent`.
[Step03_BedrockAgent_Functions](./BedrockAgent/Step03_BedrockAgent_Functions.cs)|How to use kernel functions with a `BedrockAgent`.
[Step04_BedrockAgent_Trace](./BedrockAgent/Step04_BedrockAgent_Trace.cs)|How to enable tracing for a `BedrockAgent` to inspect the chain of thoughts.
[Step05_BedrockAgent_FileSearch](./BedrockAgent/Step05_BedrockAgent_FileSearch.cs)|How to use file search with a `BedrockAgent` (i.e. Bedrock knowledge base).
[Step06_BedrockAgent_AgentChat](./BedrockAgent/Step06_BedrockAgent_AgentChat.cs)|How to create a conversation between two agents and one of them in a `BedrockAgent`.
### CopilotStudio Agent
Example|Description
---|---
[Step01_CopilotStudioAgent](./CopilotStudioAgent/Step01_CopilotStudioAgent.cs)|How to create a `CopilotStudioAgent` and interact with it in the most basic way.
[Step02_CopilotStudioAgent_Thread](./CopilotStudioAgent/Step02_CopilotStudioAgent_Thread.cs)|How to use `CopilotStudioAgent` with an `AgentThread`.
[Step03_CopilotStudioAgent_WebSearch](./CopilotStudioAgent/Step03_CopilotStudioAgent_WebSearch.cs)|How to use `CopilotStudioAgent` with web-search enabled.
### Orchestration
Example|Description
---|---
[Step01_Concurrent](./Orchestration/Step01_Concurrent.cs)|How to use a concurrent orchestration..
[Step01a_ConcurrentWithStructuredOutput](./Orchestration/Step01a_ConcurrentWithStructuredOutput.cs)|How to use structured output (with concurrent orchestration).
[Step02_Sequential](./Orchestration/Step02_Sequential.cs)|How to use sequential orchestration.
[Step02a_Sequential](./Orchestration/Step02a_Sequential.cs)|How to cancel an orchestration (with sequential orchestration).
[Step03_GroupChat](./Orchestration/Step03_GroupChat.cs)|How to use group-chat orchestration.
[Step03a_GroupChatWithHumanInTheLoop](./Orchestration/Step03a_GroupChatWithHumanInTheLoop.cs)|How to use group-chat orchestration with human in the loop.
[Step03b_GroupChatWithAIManager](./Orchestration/Step03b_GroupChatWithAIManager.cs)|How to use group-chat orchestration with a AI powered group-manager.
[Step04_Handoff](./Orchestration/Step04_Handoff.cs)|How to use handoff orchestration.
[Step04b_HandoffWithStructuredInput](./Orchestration/Step04b_HandoffWithStructuredInput.cs)|How to use structured input (with handoff orchestration).
## Legacy Agents
Support for the OpenAI Assistant API was originally published in `Microsoft.SemanticKernel.Experimental.Agents` package:
[Microsoft.SemanticKernel.Experimental.Agents](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/Experimental/Agents)
This package has been superseded by _Semantic Kernel Agents_, which includes support for Open AI Assistant agents.
## Running Examples with Filters
Examples may be explored and ran within _Visual Studio_ using _Test Explorer_.
You can also run specific examples via the command-line by using test filters (`dotnet test --filter`). Type `dotnet test --help` at the command line for more details.
Example:
```
dotnet test --filter Step3_Chat
```
## Configuring Secrets
Each example requires secrets / credentials to access OpenAI or Azure OpenAI.
We suggest using .NET [Secret Manager](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) to avoid the risk of leaking secrets into the repository, branches and pull requests. You can also use environment variables if you prefer.
To set your secrets with .NET Secret Manager:
1. Navigate the console to the project folder:
```
cd dotnet/samples/GettingStartedWithAgents
```
2. Examine existing secret definitions:
```
dotnet user-secrets list
```
3. If needed, perform first time initialization:
```
dotnet user-secrets init
```
4. Define secrets for either Open AI:
```
dotnet user-secrets set "OpenAI:ChatModelId" "..."
dotnet user-secrets set "OpenAI:ApiKey" "..."
```
5. Or Azure OpenAI:
```
dotnet user-secrets set "AzureOpenAI:ChatDeploymentName" "gpt-4o"
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://... .openai.azure.com/"
dotnet user-secrets set "AzureOpenAI:ApiKey" "..."
```
6. Or Azure AI:
```
dotnet user-secrets set "AzureAI:Endpoint" "..."
dotnet user-secrets set "AzureAI:ChatModelId" "gpt-4o"
```
7. Or Bedrock:
```
dotnet user-secrets set "BedrockAgent:AgentResourceRoleArn" "arn:aws:iam::...:role/..."
dotnet user-secrets set "BedrockAgent:FoundationModel" "..."
```
> NOTE: Azure secrets will take precedence, if both Open AI and Azure OpenAI secrets are defined, unless `ForceOpenAI` is set:
```
protected override bool ForceOpenAI => true;
```
@@ -0,0 +1,7 @@
name: ToolAgent
template_format: semantic-kernel
description: An agent that is configured to auto-invoke plugins.
execution_settings:
default:
function_choice_behavior:
type: auto
@@ -0,0 +1,17 @@
name: GenerateStory
template: |
Tell a story about {{$topic}} that is {{$length}} sentences long.
template_format: semantic-kernel
description: A function that generates a story about a topic.
input_variables:
- name: topic
description: The topic of the story.
is_required: true
- name: length
description: The number of sentences in the story.
is_required: true
output_variable:
description: The generated story.
execution_settings:
default:
temperature: 0.6
@@ -0,0 +1,13 @@
On a dark winter night, a ghost walks the ramparts of Elsinore Castle in Denmark. Discovered first by a pair of watchmen, then by the scholar Horatio, the ghost resembles the recently deceased King Hamlet, whose brother Claudius has inherited the throne and married the kings widow, Queen Gertrude. When Horatio and the watchmen bring Prince Hamlet, the son of Gertrude and the dead king, to see the ghost, it speaks to him, declaring ominously that it is indeed his fathers spirit, and that he was murdered by none other than Claudius. Ordering Hamlet to seek revenge on the man who usurped his throne and married his wife, the ghost disappears with the dawn.
Prince Hamlet devotes himself to avenging his fathers death, but, because he is contemplative and thoughtful by nature, he delays, entering into a deep melancholy and even apparent madness. Claudius and Gertrude worry about the princes erratic behavior and attempt to discover its cause. They employ a pair of Hamlets friends, Rosencrantz and Guildenstern, to watch him. When Polonius, the pompous Lord Chamberlain, suggests that Hamlet may be mad with love for his daughter, Ophelia, Claudius agrees to spy on Hamlet in conversation with the girl. But though Hamlet certainly seems mad, he does not seem to love Ophelia: he orders her to enter a nunnery and declares that he wishes to ban marriages.
A group of traveling actors comes to Elsinore, and Hamlet seizes upon an idea to test his uncles guilt. He will have the players perform a scene closely resembling the sequence by which Hamlet imagines his uncle to have murdered his father, so that if Claudius is guilty, he will surely react. When the moment of the murder arrives in the theater, Claudius leaps up and leaves the room. Hamlet and Horatio agree that this proves his guilt. Hamlet goes to kill Claudius but finds him praying. Since he believes that killing Claudius while in prayer would send Claudiuss soul to heaven, Hamlet considers that it would be an inadequate revenge and decides to wait. Claudius, now frightened of Hamlets madness and fearing for his own safety, orders that Hamlet be sent to England at once.
Hamlet goes to confront his mother, in whose bedchamber Polonius has hidden behind a tapestry. Hearing a noise from behind the tapestry, Hamlet believes the king is hiding there. He draws his sword and stabs through the fabric, killing Polonius. For this crime, he is immediately dispatched to England with Rosencrantz and Guildenstern. However, Claudiuss plan for Hamlet includes more than banishment, as he has given Rosencrantz and Guildenstern sealed orders for the King of England demanding that Hamlet be put to death.
In the aftermath of her fathers death, Ophelia goes mad with grief and drowns in the river. Poloniuss son, Laertes, who has been staying in France, returns to Denmark in a rage. Claudius convinces him that Hamlet is to blame for his fathers and sisters deaths. When Horatio and the king receive letters from Hamlet indicating that the prince has returned to Denmark after pirates attacked his ship en route to England, Claudius concocts a plan to use Laertes desire for revenge to secure Hamlets death. Laertes will fence with Hamlet in innocent sport, but Claudius will poison Laertes blade so that if he draws blood, Hamlet will die. As a backup plan, the king decides to poison a goblet, which he will give Hamlet to drink should Hamlet score the first or second hits of the match. Hamlet returns to the vicinity of Elsinore just as Ophelias funeral is taking place. Stricken with grief, he attacks Laertes and declares that he had in fact always loved Ophelia. Back at the castle, he tells Horatio that he believes one must be prepared to die, since death can come at any moment. A foolish courtier named Osric arrives on Claudiuss orders to arrange the fencing match between Hamlet and Laertes.
The sword-fighting begins. Hamlet scores the first hit, but declines to drink from the kings proffered goblet. Instead, Gertrude takes a drink from it and is swiftly killed by the poison. Laertes succeeds in wounding Hamlet, though Hamlet does not die of the poison immediately. First, Laertes is cut by his own swords blade, and, after revealing to Hamlet that Claudius is responsible for the queens death, he dies from the blades poison. Hamlet then stabs Claudius through with the poisoned sword and forces him to drink down the rest of the poisoned wine. Claudius dies, and Hamlet dies immediately after achieving his revenge.
At this moment, a Norwegian prince named Fortinbras, who has led an army to Denmark and attacked Poland earlier in the play, enters with ambassadors from England, who report that Rosencrantz and Guildenstern are dead. Fortinbras is stunned by the gruesome sight of the entire royal family lying sprawled on the floor dead. He moves to take power of the kingdom. Horatio, fulfilling Hamlets last request, tells him Hamlets tragic story. Fortinbras orders that Hamlet be carried away in a manner befitting a fallen soldier.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,46 @@
{
"openapi": "3.1.0",
"info": {
"title": "RestCountries.NET API",
"description": "Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com.",
"version": "v3.1"
},
"servers": [
{ "url": "https://restcountries.net" }
],
"auth": [],
"paths": {
"/v3.1/currency": {
"get": {
"description": "Search by currency.",
"operationId": "LookupCountryByCurrency",
"parameters": [
{
"name": "currency",
"in": "query",
"description": "The currency to search for.",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": {
"schemes": {}
}
}
@@ -0,0 +1,62 @@
{
"openapi": "3.1.0",
"info": {
"title": "get weather data",
"description": "Retrieves current weather data for a location based on wttr.in.",
"version": "v1.0.0"
},
"servers": [
{
"url": "https://wttr.in"
}
],
"auth": [],
"paths": {
"/{location}": {
"get": {
"description": "Get weather information for a specific location",
"operationId": "GetCurrentWeather",
"parameters": [
{
"name": "location",
"in": "path",
"description": "City or location to retrieve the weather for",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "format",
"in": "query",
"description": "Always use j1 value for this parameter",
"required": true,
"schema": {
"type": "string",
"default": "j1"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"404": {
"description": "Location not found"
}
},
"deprecated": false
}
}
},
"components": {
"schemes": {}
}
}
@@ -0,0 +1,190 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace GettingStarted;
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// eliciting its response to three explicit user messages.
/// </summary>
public class Step01_Agent(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ParrotName = "Parrot";
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
private const string JokerName = "Joker";
private const string JokerInstructions = "You are good at telling jokes.";
/// <summary>
/// Demonstrate the usage of <see cref="ChatCompletionAgent"/> where each invocation is
/// a unique interaction with no conversation history between them.
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseSingleChatCompletionAgent(bool useChatClient)
{
Kernel kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient);
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = ParrotName,
Instructions = ParrotInstructions,
Kernel = kernel
};
// Respond to user input
await InvokeAgentAsync("Fortune favors the bold.");
await InvokeAgentAsync("I came, I saw, I conquered.");
await InvokeAgentAsync("Practice makes perfect.");
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(message))
{
this.WriteAgentChatMessage(response);
}
}
}
/// <summary>
/// Demonstrate the usage of <see cref="ChatCompletionAgent"/> where a conversation history is maintained.
/// </summary>
[Fact]
public async Task UseSingleChatCompletionAgentWithConversation()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = JokerName,
Instructions = JokerInstructions,
Kernel = this.CreateKernelWithChatCompletion(),
};
// Define a thread variable to maintain the conversation context.
// Since we are passing a null thread to InvokeAsync on the first invocation,
// the agent will create a new thread for the conversation.
AgentThread? thread = null;
// Respond to user input
await InvokeAgentAsync("Tell me a joke about a pirate.");
await InvokeAgentAsync("Now add some emojis to the joke.");
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
thread = response.Thread;
}
}
}
/// <summary>
/// Demonstrate the usage of <see cref="ChatCompletionAgent"/> where a conversation history is maintained
/// and where the thread containing the conversation is created manually.
/// </summary>
[Fact]
public async Task UseSingleChatCompletionAgentWithManuallyCreatedThread()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = JokerName,
Instructions = JokerInstructions,
Kernel = this.CreateKernelWithChatCompletion(),
};
// Define a thread variable to maintain the conversation context.
// Since we are creating the thread, we can pass some initial messages to it.
AgentThread? thread = new ChatHistoryAgentThread(
[
new ChatMessageContent(AuthorRole.User, "Tell me a joke about a pirate."),
new ChatMessageContent(AuthorRole.Assistant, "Why did the pirate go to school? Because he wanted to improve his \"arrrrrrrrrticulation\""),
]);
// Respond to user input
await InvokeAgentAsync("Now add some emojis to the joke.");
await InvokeAgentAsync("Now make the joke sillier.");
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
// Use the thread we created earlier to continue the conversation.
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
}
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseTemplateForChatCompletionAgent(bool useChatClient)
{
// Define the agent
string generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml);
KernelPromptTemplateFactory templateFactory = new();
// Instructions, Name and Description properties defined via the config.
ChatCompletionAgent agent =
new(templateConfig, templateFactory)
{
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
Arguments = new()
{
{ "topic", "Dog" },
{ "length", "3" },
}
};
// Invoke the agent with the default arguments.
await InvokeAgentAsync();
// Invoke the agent with the override arguments.
await InvokeAgentAsync(
new()
{
{ "topic", "Cat" },
{ "length", "3" },
});
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(KernelArguments? arguments = null)
{
// Invoke the agent without any messages, since the agent has all that it needs via the template and arguments.
await foreach (ChatMessageContent content in agent.InvokeAsync(options: new() { KernelArguments = arguments }))
{
WriteAgentChatMessage(content);
}
}
}
}
@@ -0,0 +1,194 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
using Resources;
namespace GettingStarted;
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> with a <see cref="KernelPlugin"/>,
/// and then eliciting its response to explicit user messages.
/// </summary>
public class Step02_Plugins(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithPlugin(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
plugin: KernelPluginFactory.CreateFromType<MenuPlugin>(),
instructions: "Answer questions about the menu.",
name: "Host",
useChatClient: useChatClient);
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Hello");
await InvokeAgentAsync(agent, thread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, thread, "What is the special drink and its price?");
await InvokeAgentAsync(agent, thread, "Thank you");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithPluginEnumParameter(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
KernelPluginFactory.CreateFromType<WidgetFactory>(),
useChatClient: useChatClient);
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithPromptFunction(bool useChatClient)
{
// Define prompt function
KernelFunction promptFunction =
KernelFunctionFactory.CreateFromPrompt(
promptTemplate:
"""
Count the number of vowels in INPUT and report as a markdown table.
INPUT:
{{$input}}
""",
description: "Counts the number of vowels");
// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
KernelPluginFactory.CreateFromFunctions("AgentPlugin", [promptFunction]),
instructions: "You job is to only and always analyze the vowels in the user input without confirmation.",
useChatClient: useChatClient);
// Add a filter to the agent's kernel to log function invocations.
agent.Kernel.FunctionInvocationFilters.Add(new PromptFunctionFilter());
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Who would know naught of art must learn, act, and then take his ease.");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithTemplateExecutionSettings(bool useChatClient)
{
// Read the template resource
string autoInvokeYaml = EmbeddedResource.Read("AutoInvokeTools.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(autoInvokeYaml);
KernelPromptTemplateFactory templateFactory = new();
// Define the agent:
// Execution-settings with auto-invocation of plugins defined via the config.
ChatCompletionAgent agent =
new(templateConfig, templateFactory)
{
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
agent.Kernel.Plugins.AddFromType<WidgetFactory>();
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");
chatClient?.Dispose();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithManualFunctionCalling(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
KernelPluginFactory.CreateFromType<MenuPlugin>(),
functionChoiceBehavior: FunctionChoiceBehavior.Auto(autoInvoke: false),
useChatClient: useChatClient);
/// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, thread, "What is the special drink and its price?");
}
private ChatCompletionAgent CreateAgentWithPlugin(
KernelPlugin plugin,
string? instructions = null,
string? name = null,
FunctionChoiceBehavior? functionChoiceBehavior = null,
bool useChatClient = false)
{
ChatCompletionAgent agent =
new()
{
Instructions = instructions,
Name = name,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out _),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = functionChoiceBehavior ?? FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(plugin);
return agent;
}
private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistoryAgentThread thread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
Task<FunctionResultContent>[] functionResults = await ProcessFunctionCalls(response, agent.Kernel).ToArrayAsync();
thread.ChatHistory.Add(response);
foreach (ChatMessageContent functionResult in functionResults.Select(result => result.Result.ToChatMessage()))
{
this.WriteAgentChatMessage(functionResult);
thread.ChatHistory.Add(functionResult);
}
}
}
private async IAsyncEnumerable<Task<FunctionResultContent>> ProcessFunctionCalls(ChatMessageContent response, Kernel kernel)
{
foreach (FunctionCallContent functionCall in response.Items.OfType<FunctionCallContent>())
{
yield return functionCall.InvokeAsync(kernel);
}
}
private sealed class PromptFunctionFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"INVOKING: {context.Function.Name}");
await next.Invoke(context);
System.Console.WriteLine($"RESULT: {context.Result}");
}
}
}
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted;
/// <summary>
/// Demonstrate creation of <see cref="AgentChat"/> with <see cref="AgentGroupChatSettings"/>
/// that inform how chat proceeds with regards to: Agent selection, chat continuation, and maximum
/// number of agent interactions.
/// </summary>
public class Step03_Chat(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ReviewerName = "ArtDirector";
private const string ReviewerInstructions =
"""
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.
""";
private const string CopyWriterName = "CopyWriter";
private const string CopyWriterInstructions =
"""
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.
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseAgentGroupChatWithTwoAgents(bool useChatClient)
{
// Define the agents
ChatCompletionAgent agentReviewer =
new()
{
Instructions = ReviewerInstructions,
Name = ReviewerName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient1),
};
ChatCompletionAgent agentWriter =
new()
{
Instructions = CopyWriterInstructions,
Name = CopyWriterName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient2),
};
// Create a chat for agent interaction.
AgentGroupChat chat =
new(agentWriter, agentReviewer)
{
ExecutionSettings =
new()
{
// Here a TerminationStrategy subclass is used that will terminate when
// an assistant message contains the term "approve".
TerminationStrategy =
new ApprovalTerminationStrategy()
{
// Only the art-director may approve.
Agents = [agentReviewer],
// Limit total number of turns
MaximumIterations = 10,
}
}
};
// Invoke chat and display messages.
ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons.");
chat.AddChatMessage(input);
this.WriteAgentChatMessage(input);
await foreach (ChatMessageContent response in chat.InvokeAsync())
{
this.WriteAgentChatMessage(response);
}
Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]");
chatClient1?.Dispose();
chatClient2?.Dispose();
}
private sealed class ApprovalTerminationStrategy : TerminationStrategy
{
// Terminate when the final message contains the term "approve"
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken)
=> Task.FromResult(history[history.Count - 1].Content?.Contains("approve", StringComparison.OrdinalIgnoreCase) ?? false);
}
}
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted;
/// <summary>
/// Demonstrate usage of <see cref="KernelFunctionTerminationStrategy"/> and <see cref="KernelFunctionSelectionStrategy"/>
/// to manage <see cref="AgentGroupChat"/> execution.
/// </summary>
public class Step04_KernelFunctionStrategies(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ReviewerName = "ArtDirector";
private const string ReviewerInstructions =
"""
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 examples.
""";
private const string CopyWriterName = "CopyWriter";
private const string CopyWriterInstructions =
"""
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.
Never delimit the response with quotation marks.
You're laser focused on the goal at hand.
Don't waste time with chit chat.
Consider suggestions when refining an idea.
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseKernelFunctionStrategiesWithAgentGroupChat(bool useChatClient)
{
// Define the agents
ChatCompletionAgent agentReviewer =
new()
{
Instructions = ReviewerInstructions,
Name = ReviewerName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient1),
};
ChatCompletionAgent agentWriter =
new()
{
Instructions = CopyWriterInstructions,
Name = CopyWriterName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient2),
};
KernelFunction terminationFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
"""
Determine if the copy has been approved. If so, respond with a single word: yes
History:
{{$history}}
""",
safeParameterNames: "history");
KernelFunction selectionFunction =
AgentGroupChat.CreatePromptFunctionForStrategy(
$$$"""
Determine which participant takes the next turn in a conversation based on the the most recent participant.
State only the name of the participant to take the next turn.
No participant should take more than one turn in a row.
Choose only from these participants:
- {{{ReviewerName}}}
- {{{CopyWriterName}}}
Always follow these rules when selecting the next participant:
- After {{{CopyWriterName}}}, it is {{{ReviewerName}}}'s turn.
- After {{{ReviewerName}}}, it is {{{CopyWriterName}}}'s turn.
History:
{{$history}}
""",
safeParameterNames: "history");
// Limit history used for selection and termination to the most recent message.
ChatHistoryTruncationReducer strategyReducer = new(1);
// Create a chat for agent interaction.
AgentGroupChat chat =
new(agentWriter, agentReviewer)
{
ExecutionSettings =
new()
{
// Here KernelFunctionTerminationStrategy will terminate
// when the art-director has given their approval.
TerminationStrategy =
new KernelFunctionTerminationStrategy(terminationFunction, CreateKernelWithChatCompletion())
{
// Only the art-director may approve.
Agents = [agentReviewer],
// Customer result parser to determine if the response is "yes"
ResultParser = (result) => result.GetValue<string>()?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false,
// The prompt variable name for the history argument.
HistoryVariableName = "history",
// Limit total number of turns
MaximumIterations = 10,
// Save tokens by not including the entire history in the prompt
HistoryReducer = strategyReducer,
},
// Here a KernelFunctionSelectionStrategy selects agents based on a prompt function.
SelectionStrategy =
new KernelFunctionSelectionStrategy(selectionFunction, CreateKernelWithChatCompletion())
{
// Always start with the writer agent.
InitialAgent = agentWriter,
// Returns the entire result value as a string.
ResultParser = (result) => result.GetValue<string>() ?? CopyWriterName,
// The prompt variable name for the history argument.
HistoryVariableName = "history",
// Save tokens by not including the entire history in the prompt
HistoryReducer = strategyReducer,
// Only include the agent names and not the message content
EvaluateNameOnly = true,
},
}
};
// Invoke chat and display messages.
ChatMessageContent message = new(AuthorRole.User, "concept: maps made out of egg cartons.");
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in chat.InvokeAsync())
{
this.WriteAgentChatMessage(response);
}
Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]");
chatClient1?.Dispose();
chatClient2?.Dispose();
}
}
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace GettingStarted;
/// <summary>
/// Demonstrate parsing JSON response.
/// </summary>
public class Step05_JsonResult(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const int ScoreCompletionThreshold = 70;
private const string TutorName = "Tutor";
private const string TutorInstructions =
"""
Think step-by-step and rate the user input on creativity and expressiveness from 1-100.
Respond in JSON format with the following JSON schema:
{
"score": "integer (1-100)",
"notes": "the reason for your score"
}
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseKernelFunctionStrategiesWithJsonResult(bool useChatClient)
{
// Define the agents
ChatCompletionAgent agent =
new()
{
Instructions = TutorInstructions,
Name = TutorName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Create a chat for agent interaction.
AgentGroupChat chat =
new()
{
ExecutionSettings =
new()
{
// Here a TerminationStrategy subclass is used that will terminate when
// the response includes a score that is greater than or equal to 70.
TerminationStrategy = new ThresholdTerminationStrategy()
}
};
// Respond to user input
await InvokeAgentAsync("The sunset is very colorful.");
await InvokeAgentAsync("The sunset is setting over the mountains.");
await InvokeAgentAsync("The sunset is setting over the mountains and filled the sky with a deep red flame, setting the clouds ablaze.");
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
this.WriteAgentChatMessage(response);
Console.WriteLine($"[IS COMPLETED: {chat.IsComplete}]");
}
}
}
private record struct WritingScore(int score, string notes);
private sealed class ThresholdTerminationStrategy : TerminationStrategy
{
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken)
{
string lastMessageContent = history[history.Count - 1].Content ?? string.Empty;
WritingScore? result = JsonResultTranslator.Translate<WritingScore>(lastMessageContent);
return Task.FromResult((result?.score ?? 0) >= ScoreCompletionThreshold);
}
}
}
@@ -0,0 +1,147 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted;
/// <summary>
/// Demonstrate creation of an agent via dependency injection.
/// </summary>
public class Step06_DependencyInjection(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string TutorName = "Tutor";
private const string TutorInstructions =
"""
Think step-by-step and rate the user input on creativity and expressiveness from 1-100.
Respond in JSON format with the following JSON schema:
{
"score": "integer (1-100)",
"notes": "the reason for your score"
}
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseDependencyInjectionToCreateAgent(bool useChatClient)
{
ServiceCollection serviceContainer = new();
serviceContainer.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
if (useChatClient)
{
IChatClient chatClient;
if (this.UseOpenAIConfig)
{
chatClient = new OpenAI.OpenAIClient(TestConfiguration.OpenAI.ApiKey)
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient();
}
else if (!string.IsNullOrEmpty(this.ApiKey))
{
chatClient = new AzureOpenAIClient(
endpoint: new Uri(TestConfiguration.AzureOpenAI.Endpoint),
credential: new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient();
}
else
{
chatClient = new AzureOpenAIClient(
endpoint: new Uri(TestConfiguration.AzureOpenAI.Endpoint),
credential: new AzureCliCredential())
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient();
}
var functionCallingChatClient = chatClient!.AsBuilder().UseKernelFunctionInvocation().Build();
serviceContainer.AddTransient<IChatClient>((sp) => functionCallingChatClient);
}
else
{
if (this.UseOpenAIConfig)
{
serviceContainer.AddOpenAIChatCompletion(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey);
}
else if (!string.IsNullOrEmpty(this.ApiKey))
{
serviceContainer.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
}
else
{
serviceContainer.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
new AzureCliCredential());
}
}
// Transient Kernel as each agent may customize its Kernel instance with plug-ins.
serviceContainer.AddTransient<Kernel>();
serviceContainer.AddTransient<AgentClient>();
serviceContainer.AddKeyedSingleton<ChatCompletionAgent>(
TutorName,
(sp, key) =>
new ChatCompletionAgent()
{
Instructions = TutorInstructions,
Name = TutorName,
Kernel = sp.GetRequiredService<Kernel>().Clone(),
});
// Create a service provider for resolving registered services
await using ServiceProvider serviceProvider = serviceContainer.BuildServiceProvider();
// If an application follows DI guidelines, the following line is unnecessary because DI will inject an instance of the AgentClient class to a class that references it.
// DI container guidelines - https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#recommendations
AgentClient agentClient = serviceProvider.GetRequiredService<AgentClient>();
// Execute the agent-client
await WriteAgentResponse("The sunset is nice.");
await WriteAgentResponse("The sunset is setting over the mountains.");
await WriteAgentResponse("The sunset is setting over the mountains and filled the sky with a deep red flame, setting the clouds ablaze.");
// Local function to invoke agent and display the conversation messages.
async Task WriteAgentResponse(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agentClient.RunDemoAsync(message))
{
this.WriteAgentChatMessage(response);
}
}
}
private sealed class AgentClient([FromKeyedServices(TutorName)] ChatCompletionAgent agent)
{
private readonly AgentGroupChat _chat = new();
public IAsyncEnumerable<ChatMessageContent> RunDemoAsync(ChatMessageContent input)
{
this._chat.AddChatMessage(input);
return this._chat.InvokeAsync(agent);
}
}
private record struct WritingScore(int score, string notes);
}
@@ -0,0 +1,245 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace GettingStarted;
/// <summary>
/// A repeat of <see cref="Step03_Chat"/> with telemetry enabled.
/// </summary>
public class Step07_Telemetry(ITestOutputHelper output) : BaseAssistantTest(output)
{
/// <summary>
/// Instance of <see cref="ActivitySource"/> for the example's main activity.
/// </summary>
private static readonly ActivitySource s_activitySource = new("AgentsTelemetry.Example");
/// <summary>
/// Demonstrates logging in <see cref="ChatCompletionAgent"/>, <see cref="OpenAIAssistantAgent"/> and <see cref="AgentGroupChat"/>.
/// Logging is enabled through the <see cref="Agent.LoggerFactory"/> and <see cref="AgentChat.LoggerFactory"/> properties.
/// This example uses <see cref="XunitLogger"/> to output logs to the test console, but any compatible logging provider can be used.
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Logging(bool useChatClient)
{
await RunExampleAsync(loggerFactory: this.LoggerFactory, useChatClient: useChatClient);
// Output:
// [AddChatMessages] Adding Messages: 1.
// [AddChatMessages] Added Messages: 1.
// [InvokeAsync] Invoking chat: Microsoft.SemanticKernel.Agents.ChatCompletionAgent:63c505e8-cf5b-4aa3-a6a5-067a52377f82/CopyWriter, Microsoft.SemanticKernel.Agents.ChatCompletionAgent:85f6777b-54ef-4392-9608-67bc85c42c5b/ArtDirector
// [InvokeAsync] Selecting agent: Microsoft.SemanticKernel.Agents.Chat.SequentialSelectionStrategy.
// [NextAsync] Selected agent (0 / 2): 63c505e8-cf5b-4aa3-a6a5-067a52377f82/CopyWriter
// and more...
}
/// <summary>
/// Demonstrates tracing in <see cref="ChatCompletionAgent"/> and <see cref="OpenAIAssistantAgent"/>.
/// Tracing is enabled through the <see cref="TracerProvider"/>.
/// For output this example uses Console as well as Application Insights.
/// </summary>
[Theory]
[InlineData(true, false, false)]
[InlineData(false, false, false)]
[InlineData(true, true, false)]
[InlineData(false, true, false)]
[InlineData(true, false, true)]
[InlineData(false, false, true)]
[InlineData(true, true, true)]
[InlineData(false, true, true)]
public async Task Tracing(bool useApplicationInsights, bool useStreaming, bool useChatClient)
{
using var tracerProvider = GetTracerProvider(useApplicationInsights);
using var activity = s_activitySource.StartActivity("MainActivity");
Console.WriteLine($"Operation/Trace ID: {Activity.Current?.TraceId}");
await RunExampleAsync(useStreaming: useStreaming, useChatClient: useChatClient);
// Output:
// Operation/Trace ID: 132d831ef39c13226cdaa79873f375b8
// Activity.TraceId: 132d831ef39c13226cdaa79873f375b8
// Activity.SpanId: 891e8f2f32a61123
// Activity.TraceFlags: Recorded
// Activity.ParentSpanId: 5dae937c9438def9
// Activity.ActivitySourceName: Microsoft.SemanticKernel.Diagnostics
// Activity.DisplayName: chat.completions gpt-4
// Activity.Kind: Client
// Activity.StartTime: 2025-02-03T23:32:57.1363560Z
// Activity.Duration: 00:00:02.1339320
// and more...
}
#region private
private async Task RunExampleAsync(
bool useStreaming = false,
ILoggerFactory? loggerFactory = null,
bool useChatClient = false)
{
// Define the agents
ChatCompletionAgent agentReviewer =
new()
{
Name = "ArtDirector",
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 examples.
""",
Description = "An art director who has opinions about copywriting born of a love for David Ogilvy",
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
LoggerFactory = GetLoggerFactoryOrDefault(loggerFactory),
};
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: "CopyWriter",
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.
""",
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agentWriter = new(assistant, this.AssistantClient)
{
LoggerFactory = GetLoggerFactoryOrDefault(loggerFactory)
};
// Create a chat for agent interaction.
AgentGroupChat chat =
new(agentWriter, agentReviewer)
{
// This is all that is required to enable logging across the Agent Framework.
LoggerFactory = GetLoggerFactoryOrDefault(loggerFactory),
ExecutionSettings =
new()
{
// Here a TerminationStrategy subclass is used that will terminate when
// an assistant message contains the term "approve".
TerminationStrategy =
new ApprovalTerminationStrategy()
{
// Only the art-director may approve.
Agents = [agentReviewer],
// Limit total number of turns
MaximumIterations = 10,
}
}
};
// Invoke chat and display messages.
ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons.");
chat.AddChatMessage(input);
this.WriteAgentChatMessage(input);
if (useStreaming)
{
string lastAgent = string.Empty;
await foreach (StreamingChatMessageContent response in chat.InvokeStreamingAsync())
{
if (string.IsNullOrEmpty(response.Content))
{
continue;
}
if (!lastAgent.Equals(response.AuthorName, StringComparison.Ordinal))
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
lastAgent = response.AuthorName ?? string.Empty;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
// Display the chat history.
Console.WriteLine("================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
ChatMessageContent[] history = await chat.GetChatMessagesAsync().Reverse().ToArrayAsync();
for (int index = 0; index < history.Length; index++)
{
this.WriteAgentChatMessage(history[index]);
}
}
else
{
await foreach (ChatMessageContent response in chat.InvokeAsync())
{
this.WriteAgentChatMessage(response);
}
}
Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]");
chatClient?.Dispose();
}
private TracerProvider? GetTracerProvider(bool useApplicationInsights)
{
// Enable diagnostics.
AppContext.SetSwitch("Microsoft.SemanticKernel.Experimental.GenAI.EnableOTelDiagnostics", true);
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Semantic Kernel Agents Tracing Example"))
.AddSource("Microsoft.SemanticKernel*")
.AddSource(s_activitySource.Name);
if (useApplicationInsights)
{
var connectionString = TestConfiguration.ApplicationInsights.ConnectionString;
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ConfigurationNotFoundException(
nameof(TestConfiguration.ApplicationInsights),
nameof(TestConfiguration.ApplicationInsights.ConnectionString));
}
tracerProviderBuilder.AddAzureMonitorTraceExporter(o => o.ConnectionString = connectionString);
}
else
{
tracerProviderBuilder.AddConsoleExporter();
}
return tracerProviderBuilder.Build();
}
private ILoggerFactory GetLoggerFactoryOrDefault(ILoggerFactory? loggerFactory = null) => loggerFactory ?? NullLoggerFactory.Instance;
private sealed class ApprovalTerminationStrategy : TerminationStrategy
{
// Terminate when the final message contains the term "approve"
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken)
=> Task.FromResult(history[history.Count - 1].Content?.Contains("approve", StringComparison.OrdinalIgnoreCase) ?? false);
}
#endregion
}
@@ -0,0 +1,166 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace GettingStarted;
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// eliciting its response to three explicit user messages.
/// </summary>
public class Step08_AgentAsKernelFunction(ITestOutputHelper output) : BaseAgentsTest(output)
{
protected override bool ForceOpenAI { get; } = true;
[Fact]
public async Task SalesAssistantAgent()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
kernel.Plugins.AddFromType<OrderPlugin>();
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output));
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = "SalesAssistant",
Instructions = "You are a sales assistant. Place orders for items the user requests.",
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Invoke the agent and display the responses
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "Place an order for a black boot."));
await foreach (ChatMessageContent responseItem in responseItems)
{
this.WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task RefundAgent()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
kernel.Plugins.AddFromType<RefundPlugin>();
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output));
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = "RefundAgent",
Instructions = "You are a refund agent. Help the user with refunds.",
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Invoke the agent and display the responses
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "I want a refund for a black boot."));
await foreach (ChatMessageContent responseItem in responseItems)
{
this.WriteAgentChatMessage(responseItem);
}
}
[Fact]
public async Task MultipleAgents()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
KernelPlugin agentPlugin = AgentKernelPluginFactory.CreateFromAgents("AgentPlugin",
[
this.CreateSalesAssistant(),
this.CreateRefundAgent()
]);
kernel.Plugins.Add(agentPlugin);
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output));
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = "ShoppingAssistant",
Instructions = "You are a sales assistant. Delegate to the provided agents to help the user with placing orders and requesting refunds.",
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Invoke the agent and display the responses
string[] messages =
[
"Place an order for a black boot.",
"Now I want a refund for the black boot."
];
AgentThread? agentThread = null;
foreach (var message in messages)
{
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, message), agentThread);
await foreach (var responseItem in responseItems)
{
agentThread = responseItem.Thread;
this.WriteAgentChatMessage(responseItem.Message);
}
}
}
#region private
private ChatCompletionAgent CreateSalesAssistant()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
kernel.Plugins.AddFromType<OrderPlugin>();
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output));
// Define the agent
return new()
{
Name = "SalesAssistant",
Instructions = "You are a sales assistant. Place orders for items the user requests.",
Description = "Agent to invoke to place orders for items the user requests.",
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
}
private ChatCompletionAgent CreateRefundAgent()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
kernel.Plugins.AddFromType<RefundPlugin>();
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output));
// Define the agent
return new()
{
Name = "RefundAgent",
Instructions = "You are a refund agent. Help the user with refunds.",
Description = "Agent to invoke to execute a refund an item on behalf of the user.",
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
}
#endregion
}
public sealed class OrderPlugin
{
[KernelFunction, Description("Place an order for the specified item.")]
public string PlaceOrder([Description("The name of the item to be ordered.")] string itemName) => "success";
}
public sealed class RefundPlugin
{
[KernelFunction, Description("Execute a refund for the specified item.")]
public string ExecuteRefund(string itemName) => "success";
}
public sealed class AutoFunctionInvocationFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
output.WriteLine($"Invoke: {context.Function.Name}");
await next(context);
}
}
@@ -0,0 +1,124 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
namespace GettingStarted;
/// <summary>
/// This example demonstrates how to declaratively create instances of <see cref="Agent"/>.
/// </summary>
public class Step09_Declarative(ITestOutputHelper output) : BaseAgentsTest(output)
{
/// <summary>
/// Demonstrates creating and using a Chat Completion Agent with a Kernel.
/// </summary>
[Fact]
public async Task ChatCompletionAgentWithKernel()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
var text =
"""
type: chat_completion_agent
name: StoryAgent
description: Story Telling Agent
instructions: Tell a story suitable for children about the topic provided by the user.
""";
var agentFactory = new ChatCompletionAgentFactory();
var agent = await agentFactory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel });
await foreach (ChatMessageContent response in agent!.InvokeAsync("Cats and Dogs"))
{
this.WriteAgentChatMessage(response);
}
}
/// <summary>
/// Demonstrates creating and using a Chat Completion Agent with functions.
/// </summary>
[Fact]
public async Task ChatCompletionAgentWithFunctions()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
kernel.Plugins.Add(plugin);
var text =
"""
type: chat_completion_agent
name: FunctionCallingAgent
instructions: Use the provided functions to answer questions about the menu.
description: This agent uses the provided functions to answer questions about the menu.
model:
options:
temperature: 0.4
tools:
- id: MenuPlugin.GetSpecials
type: function
- id: MenuPlugin.GetItemPrice
type: function
""";
var agentFactory = new ChatCompletionAgentFactory();
var agent = await agentFactory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel });
await foreach (ChatMessageContent response in agent!.InvokeAsync(new ChatMessageContent(AuthorRole.User, "What is the special soup and how much does it cost?")))
{
this.WriteAgentChatMessage(response);
}
}
/// <summary>
/// Demonstrates creating and using a Chat Completion Agent with templated instructions.
/// </summary>
[Fact]
public async Task ChatCompletionAgentWithTemplate()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
var text =
"""
type: chat_completion_agent
name: StoryAgent
description: A agent that generates a story about a topic.
instructions: Tell a story about {{$topic}} that is {{$length}} sentences long.
inputs:
topic:
description: The topic of the story.
required: true
default: Cats
length:
description: The number of sentences in the story.
required: true
default: 2
outputs:
output1:
description: output1 description
template:
format: semantic-kernel
""";
var agentFactory = new ChatCompletionAgentFactory();
var promptTemplateFactory = new KernelPromptTemplateFactory();
var agent = await agentFactory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel, PromptTemplateFactory = promptTemplateFactory });
Assert.NotNull(agent);
var options = new AgentInvokeOptions()
{
KernelArguments = new()
{
{ "topic", "Dogs" },
{ "length", "3" },
}
};
await foreach (ChatMessageContent response in agent.InvokeAsync(Array.Empty<ChatMessageContent>(), options: options))
{
this.WriteAgentChatMessage(response);
}
}
}
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
namespace GettingStarted;
/// <summary>
/// This example demonstrates how to declaratively create instances of <see cref="Microsoft.SemanticKernel.Agents.Agent"/>.
/// </summary>
public class Step10_MultiAgent_Declarative : BaseAgentsTest
{
/// <summary>
/// Demonstrates creating and using a Chat Completion Agent with a Kernel.
/// </summary>
[Fact]
public async Task ChatCompletionAgentWithKernel()
{
Kernel kernel = this.CreateKernelWithChatCompletion();
var text =
"""
type: chat_completion_agent
name: StoryAgent
description: Story Telling Agent
instructions: Tell a story suitable for children about the topic provided by the user.
""";
var agent = await this._kernelAgentFactory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel });
await foreach (ChatMessageContent response in agent!.InvokeAsync(new ChatMessageContent(AuthorRole.User, "Cats and Dogs")))
{
this.WriteAgentChatMessage(response);
}
}
/// <summary>
/// Demonstrates creating and using an Azure AI Agent with a Kernel.
/// </summary>
[Fact]
public async Task AzureAIAgentWithKernel()
{
var text =
"""
type: foundry_agent
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${AzureAI:ChatModelId}
""";
var agent = await this._kernelAgentFactory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
Assert.NotNull(agent);
var input = "Could you please create a bar chart for the operating profit using the following data and provide the file to me? Company A: $1.2 million, Company B: $2.5 million, Company C: $3.0 million, Company D: $1.8 million";
Microsoft.SemanticKernel.Agents.AgentThread? agentThread = null;
try
{
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, input)))
{
agentThread = response.Thread;
WriteAgentChatMessage(response);
}
}
catch (Exception e)
{
Console.WriteLine($"Error invoking agent: {e.Message}");
}
finally
{
var azureaiAgent = agent as AzureAIAgent;
Assert.NotNull(azureaiAgent);
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
await agentThread.DeleteAsync();
}
}
}
public Step10_MultiAgent_Declarative(ITestOutputHelper output) : base(output)
{
var openaiClient =
this.UseOpenAIConfig ?
OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
!string.IsNullOrWhiteSpace(this.ApiKey) ?
OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));
var agentsClient = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<OpenAIClient>(openaiClient);
builder.Services.AddSingleton<PersistentAgentsClient>(agentsClient);
AddChatCompletionToKernel(builder);
this._kernel = builder.Build();
this._kernelAgentFactory =
new AggregatorAgentFactory(
new ChatCompletionAgentFactory(),
new OpenAIAssistantAgentFactory(),
new AzureAIAgentFactory());
}
#region private
private readonly Kernel _kernel;
private readonly AgentFactory _kernelAgentFactory;
#endregion
}