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
+27
View File
@@ -0,0 +1,27 @@
# Setting errors for SDK projects under samples folder
[*.cs]
indent_style = space
indent_size = 4
dotnet_diagnostic.CA2007.severity = error # Do not directly await a Task
dotnet_diagnostic.VSTHRD111.severity = error # Use .ConfigureAwait(bool)
dotnet_diagnostic.IDE1006.severity = error # Naming rule violations
dotnet_diagnostic.RCS1110.severity = none # Declare type inside namespace
dotnet_diagnostic.CA2201.severity = none # Exception is not sufficiently specific
dotnet_diagnostic.CS1998.severity = none # Async method lacks 'await' operators and will run synchronously
dotnet_diagnostic.CA1851.severity = none # Possible multiple enumerations of 'IEnumerable' collection
dotnet_diagnostic.CA1819.severity = none # Properties should not return arrays
dotnet_diagnostic.CA1812.severity = none # Avoid uninstantiated internal classes
dotnet_diagnostic.VSTHRD002.severity = none # Avoid problematic synchronous waits
dotnet_diagnostic.CS1587.severity = none # XML comment is not placed on a valid language element
dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types
dotnet_diagnostic.CA2000.severity = none # Dispose objects before losing scope
dotnet_diagnostic.RCS1110.severity = none # Declare type inside namespace
dotnet_diagnostic.CA5394.severity = none # Do not use insecure randomness
# Resharper disabled rules: https://www.jetbrains.com/help/resharper/Reference__Code_Inspections_CSHARP.html#CodeSmell
resharper_condition_is_always_true_or_false_according_to_nullable_api_contract_highlighting = none # ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
resharper_inconsistent_naming_highlighting = none # InconsistentNaming
resharper_equal_expression_comparison_highlighting = none # EqualExpressionComparison
resharper_check_namespace_highlighting = none # CheckNamespace
resharper_arrange_object_creation_when_type_not_evident_highlighting = none # Disable "Arrange object creation when type is not evident" highlighting
resharper_arrange_this_qualifier_highlighting = none # Disable "Arrange 'this.' qualifier" highlighting
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA1812;CA2007;RCS1102;VSTHRD111;VSTHRD200</NoWarn>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Orchestration\Agents.Orchestration.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Runtime\InProcess\Runtime.InProcess.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
// This sample compares running concurrent orchestrations using
// Semantic Kernel and the Agent Framework.
Console.WriteLine("=== Semantic Kernel Concurrent Orchestration ===");
await SKConcurrentOrchestration();
Console.WriteLine("\n=== Agent Framework Concurrent Agent Workflow ===");
await AFConcurrentAgentWorkflow();
# region SKConcurrentOrchestration
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
async Task SKConcurrentOrchestration()
{
ConcurrentOrchestration orchestration = new([
GetSKTranslationAgent("French"),
GetSKTranslationAgent("Spanish")])
{
StreamingResponseCallback = StreamingResultCallback,
};
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
OrchestrationResult<string[]> result = await orchestration.InvokeAsync("Hello, world!", runtime);
string[] texts = await result.GetValueAsync(TimeSpan.FromSeconds(20));
await runtime.RunUntilIdleAsync();
}
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
{
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
return new ChatCompletionAgent()
{
Kernel = kernel,
Instructions = string.Format(agentInstructions, targetLanguage),
Description = $"Agent that translates texts to {targetLanguage}",
Name = $"SKTranslationAgent_{targetLanguage}"
};
}
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
{
Console.Write(streamedResponse.Content);
if (isFinal)
{
Console.WriteLine();
}
return ValueTask.CompletedTask;
}
# endregion
# region AFConcurrentAgentWorkflow
async Task AFConcurrentAgentWorkflow()
{
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
var frenchAgent = GetAFTranslationAgent("French", client);
var spanishAgent = GetAFTranslationAgent("Spanish", client);
var concurrentAgentWorkflow = AgentWorkflowBuilder.BuildConcurrent([frenchAgent, spanishAgent]);
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(concurrentAgentWorkflow, "Hello, world!");
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
string? lastExecutorId = null;
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is AgentResponseUpdateEvent e)
{
if (string.IsNullOrEmpty(e.Update.Text))
{
continue;
}
if (e.ExecutorId != lastExecutorId)
{
lastExecutorId = e.ExecutorId;
Console.WriteLine();
Console.Write($"{e.Update.AuthorName}: ");
}
Console.Write(e.Update.Text);
}
}
}
ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
# endregion
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA1812;CA2007;RCS1102;VSTHRD111;VSTHRD200</NoWarn>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Orchestration\Agents.Orchestration.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Runtime\InProcess\Runtime.InProcess.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
// This sample compares running sequential orchestrations using
// Semantic Kernel and the Agent Framework.
Console.WriteLine("=== Semantic Kernel Sequential Orchestration ===");
await SKSequentialOrchestration();
Console.WriteLine("\n=== Agent Framework Sequential Agent Workflow ===");
await AFSequentialAgentWorkflow();
# region SKSequentialOrchestration
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
async Task SKSequentialOrchestration()
{
SequentialOrchestration orchestration = new([
GetSKTranslationAgent("French"),
GetSKTranslationAgent("Spanish"),
GetSKTranslationAgent("English")])
{
StreamingResponseCallback = StreamingResultCallback,
};
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
OrchestrationResult<string> result = await orchestration.InvokeAsync("Hello, world!", runtime);
string text = await result.GetValueAsync(TimeSpan.FromSeconds(20));
await runtime.RunUntilIdleAsync();
}
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
{
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
return new ChatCompletionAgent()
{
Kernel = kernel,
Instructions = string.Format(agentInstructions, targetLanguage),
Description = $"Agent that translates texts to {targetLanguage}",
Name = $"SKTranslationAgent_{targetLanguage}"
};
}
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
{
Console.Write(streamedResponse.Content);
if (isFinal)
{
Console.WriteLine();
}
return ValueTask.CompletedTask;
}
# endregion
# region AFSequentialAgentWorkflow
async Task AFSequentialAgentWorkflow()
{
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
var frenchAgent = GetAFTranslationAgent("French", client);
var spanishAgent = GetAFTranslationAgent("Spanish", client);
var englishAgent = GetAFTranslationAgent("English", client);
var sequentialAgentWorkflow = AgentWorkflowBuilder.BuildSequential(
[frenchAgent, spanishAgent, englishAgent]);
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(sequentialAgentWorkflow, "Hello, world!");
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
string? lastExecutorId = null;
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is AgentResponseUpdateEvent e)
{
if (string.IsNullOrEmpty(e.Update.Text))
{
continue;
}
if (e.ExecutorId != lastExecutorId)
{
lastExecutorId = e.ExecutorId;
Console.WriteLine();
Console.Write($"{e.Update.AuthorName}: ");
}
Console.Write(e.Update.Text);
}
}
}
ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
# endregion
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA1812;CA2007;RCS1102;VSTHRD111;VSTHRD200</NoWarn>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Orchestration\Agents.Orchestration.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Runtime\InProcess\Runtime.InProcess.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,249 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable MAAIW001 // Experimental: HandoffWorkflowBuilder
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
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;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Queries to simulate user input during the interactive orchestration
List<string> Queries = [
"I'd like to track the status of my first order 123.",
"I want to return another order of mine whose ID is 456 because it arrived damaged.",
];
// This sample compares running handoff orchestrations using
// Semantic Kernel and the Agent Framework.
Console.WriteLine("=== Semantic Kernel Handoff Orchestration ===");
// State to help format the streaming output
bool newAgentTurn = true;
string previousFunctionCallId = string.Empty;
await SKHandoffOrchestration();
Console.WriteLine("\n=== Agent Framework Handoff Agent Workflow ===");
await AFHandoffAgentWorkflow();
# region SKHandoffOrchestration
[KernelFunction]
string SKCheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
[KernelFunction]
string SKProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
[KernelFunction]
string SKProcessRefund(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
async Task SKHandoffOrchestration()
{
// Create agents
var triageAgent = GetSKAgent(
instructions: "You are a customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
var statusAgent = GetSKAgent(
instructions: "You are a customer support agent that checks order status.",
name: "OrderStatusAgent",
description: "Handle order status requests.");
statusAgent.Kernel.Plugins.AddFromFunctions("OrderStatusPlugin", [KernelFunctionFactory.CreateFromMethod(SKCheckOrderStatus)]);
var returnAgent = GetSKAgent(
instructions: "You are a customer support agent that handles order returns.",
name: "OrderReturnAgent",
description: "Handle order return requests.");
returnAgent.Kernel.Plugins.AddFromFunctions("OrderReturnPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessReturn)]);
var refundAgent = GetSKAgent(
instructions: "You are a customer support agent that handles order refunds.",
name: "OrderRefundAgent",
description: "Handle order refund requests.");
refundAgent.Kernel.Plugins.AddFromFunctions("OrderRefundPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessRefund)]);
Queue<string> queries = new(Queries);
// Create orchestration with handoffs
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 = queries.Count > 0 ? queries.Dequeue() : "exit";
Console.WriteLine($"\nUser: {input}");
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
},
StreamingResponseCallback = StreamingResultCallback,
};
InProcessRuntime runtime = new();
await runtime.StartAsync();
// Run the orchestration
OrchestrationResult<string> result = await orchestration.InvokeAsync(
"I am a customer that needs help with my two orders",
runtime);
string text = await result.GetValueAsync();
Console.WriteLine($"\nFinal Result: {text}");
await runtime.RunUntilIdleAsync();
}
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ChatCompletionAgent GetSKAgent(string instructions, string name, string description)
{
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
return new ChatCompletionAgent()
{
Kernel = kernel,
Instructions = instructions,
Description = description,
Name = name
};
}
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
{
if (newAgentTurn)
{
Console.Write($"\n{streamedResponse.AuthorName}: ");
newAgentTurn = false;
}
Console.Write(streamedResponse.Content);
if (streamedResponse.Items.OfType<StreamingFunctionCallUpdateContent>().FirstOrDefault()
is StreamingFunctionCallUpdateContent call)
{
if (call.CallId is not null && previousFunctionCallId != call.CallId)
{
Console.Write($"\nCalling function '{call.Name}' with arguments: ");
previousFunctionCallId = call.CallId;
}
if (!string.IsNullOrEmpty(call.Arguments))
{
Console.Write($"{call.Arguments}");
}
}
if (isFinal)
{
newAgentTurn = true;
previousFunctionCallId = string.Empty;
Console.WriteLine();
}
return ValueTask.CompletedTask;
}
# endregion
# region AFHandoffAgentWorkflow
[Description("Get the order status for a given order ID.")]
static string AFCheckOrderStatus([Description("The order ID to check the status for.")] string orderId)
=> $"Order {orderId} is shipped and will arrive in 2-3 days.";
[Description("Process a return for a given order ID.")]
static string AFProcessReturn(
[Description("The order ID to process the return for.")] string orderId,
[Description("The reason for the return.")] string reason)
=> $"Return for order {orderId} has been processed successfully for the following reason: {reason}.";
[Description("Process a refund for a given order ID.")]
static string AFProcessRefund([Description("The order ID to process the refund for.")] string orderId)
=> $"Refund for order {orderId} has been processed successfully.";
async Task AFHandoffAgentWorkflow()
{
// Create agents
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
ChatClientAgent triageAgent = new(client,
instructions: "A customer support agent that triages issues.",
name: "TriageAgent",
description: "Handle customer requests.");
ChatClientAgent statusAgent = new(client,
name: "OrderStatusAgent",
instructions: "Handle order status requests.",
description: "A customer support agent that checks order status.",
tools: [AIFunctionFactory.Create(AFCheckOrderStatus)]);
ChatClientAgent returnAgent = new(client,
name: "OrderReturnAgent",
instructions: "Handle order return requests.",
description: "A customer support agent that handles order returns.",
tools: [AIFunctionFactory.Create(AFProcessReturn)]);
ChatClientAgent refundAgent = new(client,
name: "OrderRefundAgent",
instructions: "Handle order refund requests.",
description: "A customer support agent that handles order refund.",
tools: [AIFunctionFactory.Create(AFProcessRefund)]);
// Create workflow with handoffs
var handoffAgentWorkflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
.WithHandoffs(triageAgent, [statusAgent, returnAgent, refundAgent])
.WithHandoff(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
.WithHandoff(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
.WithHandoff(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related")
.Build();
// Run the workflow
List<ChatMessage> messages = [];
foreach (var query in Queries)
{
Console.WriteLine($"User: {query}");
messages.Add(new(ChatRole.User, query));
await using var run = await InProcessExecution.RunStreamingAsync(handoffAgentWorkflow, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
string? lastExecutorId = null;
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is AgentResponseUpdateEvent e)
{
if (string.IsNullOrEmpty(e.Update.Text) && e.Update.Contents.Count == 0)
{
continue;
}
if (e.ExecutorId != lastExecutorId)
{
lastExecutorId = e.ExecutorId;
Console.WriteLine();
Console.Write($"{e.Update.AuthorName}: ");
}
Console.Write(e.Update.Text);
if (e.Update.Contents.OfType<Microsoft.Extensions.AI.FunctionCallContent>().FirstOrDefault()
is Microsoft.Extensions.AI.FunctionCallContent call)
{
Console.WriteLine();
Console.WriteLine($"Calling function '{call.Name}' with arguments: {JsonSerializer.Serialize(call.Arguments)}");
}
}
else if (evt is WorkflowOutputEvent output)
{
Console.WriteLine("\n");
messages.AddRange(output.As<List<ChatMessage>>()!);
}
}
}
}
# endregion
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA1812;CA2007;RCS1102;VSTHRD111</NoWarn>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(
deploymentName,
name: "GenerateStory",
instructions: "You are good at telling jokes.");
AzureAIAgent agent = new(definition, azureAgentClient);
var thread = new AzureAIAgentThread(azureAgentClient);
AzureAIAgentInvokeOptions options = new() { MaxPromptTokens = 1000 };
var result = await agent.InvokeAsync(userInput, thread, options).FirstAsync();
Console.WriteLine(result.Message);
Console.WriteLine("---");
await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(userInput, thread))
{
Console.Write(update);
}
// Clean up
await thread.DeleteAsync();
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(
deploymentName,
name: "GenerateStory",
instructions: "You are good at telling jokes.");
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
AzureAIAgent skAgent = new(definition, azureAgentClient);
var agent = skAgent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
// Clean up
if (thread is ChatClientAgentSession chatSession)
{
await azureAgentClient.Threads.DeleteThreadAsync(chatSession.ConversationId);
}
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
// AF 1.0: Use AIProjectClient.AsAIAgent() from Microsoft.Agents.AI.Foundry
var projectClient = new AIProjectClient(new Uri(azureEndpoint), new AzureCliCredential());
var agent = projectClient.AsAIAgent(
deploymentName,
instructions: "You are good at telling jokes.",
name: "GenerateStory");
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, session, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
Console.Write(update);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,131 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Azure.AI.Agents.Persistent;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "What is the weather like in Amsterdam?";
Console.WriteLine($"User Input: {userInput}");
[KernelFunction]
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(deploymentName, instructions: "You are a helpful assistant");
AzureAIAgent agent = new(definition, azureAgentClient)
{
Kernel = Kernel.CreateBuilder().Build(),
Name = "Host",
Instructions = "You are a helpful assistant",
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
var thread = new AzureAIAgentThread(azureAgentClient);
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
Console.WriteLine("---");
await foreach (ChatMessageContent update in agent.InvokeAsync(userInput, thread))
{
Console.Write(update);
}
// Clean up
await thread.DeleteAsync();
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(deploymentName, instructions: "You are a helpful assistant");
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
AzureAIAgent skAgent = new(definition, azureAgentClient)
{
Kernel = Kernel.CreateBuilder().Build(),
Name = "Host",
Instructions = "You are a helpful assistant",
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
skAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
var agent = skAgent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { Tools = [AIFunctionFactory.Create(GetWeather)] });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
// Clean up
if (thread is ChatClientAgentSession chatSession)
{
await azureAgentClient.Threads.DeleteThreadAsync(chatSession.ConversationId);
}
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AIProjectClient(new Uri(azureEndpoint), new AzureCliCredential())
.AsAIAgent(model: deploymentName,
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, session, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
Console.Write(update);
}
// No cleanup needed - non-hosted path doesn't create server-side resources.
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton((sp) => AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential()));
serviceCollection.AddTransient<AzureAIAgent>((sp) =>
{
var azureAgentClient = sp.GetRequiredService<PersistentAgentsClient>();
Console.Write("Creating agent in the cloud...");
PersistentAgent definition = azureAgentClient.Administration
.CreateAgent(deploymentName,
name: "GenerateStory",
instructions: "You are good at telling jokes.");
Console.Write("Done\n");
return new(definition, azureAgentClient);
});
serviceCollection.AddKernel();
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AzureAIAgent>();
var thread = new AzureAIAgentThread(agent.Client);
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
Console.WriteLine("---");
await foreach (ChatMessageContent update in agent.InvokeAsync(userInput, thread))
{
Console.Write(update);
}
// Clean up
await thread.DeleteAsync();
await agent.Client.Administration.DeleteAgentAsync(agent.Id);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton((sp) => AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential()));
serviceCollection.AddTransient<AzureAIAgent>((sp) =>
{
var azureAgentClient = sp.GetRequiredService<PersistentAgentsClient>();
Console.Write("Creating agent in the cloud...");
PersistentAgent definition = azureAgentClient.Administration
.CreateAgent(deploymentName,
name: "GenerateStory",
instructions: "You are good at telling jokes.");
Console.Write("Done\n");
return new(definition, azureAgentClient);
});
serviceCollection.AddKernel();
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var skAgent = serviceProvider.GetRequiredService<AzureAIAgent>();
var agent = skAgent.AsAIAgent();
var thread = await agent.CreateSessionAsync();
var result = await agent.RunAsync(userInput, thread);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread))
{
Console.Write(update);
}
// Clean up
var azureAgentClient = serviceProvider.GetRequiredService<PersistentAgentsClient>();
if (thread is ChatClientAgentSession chatSession)
{
await azureAgentClient.Threads.DeleteThreadAsync(chatSession.ConversationId);
}
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton((sp) => new AIProjectClient(new Uri(azureEndpoint), new AzureCliCredential()));
serviceCollection.AddTransient<AIAgent>((sp) =>
{
var client = sp.GetRequiredService<AIProjectClient>();
return client.AsAIAgent(
deploymentName,
instructions: "You are good at telling jokes.",
name: "GenerateStory");
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var session = await agent.CreateSessionAsync();
var result = await agent.RunAsync(userInput, session);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session))
{
Console.Write(update);
}
// No cleanup needed - non-hosted path doesn't create server-side resources.
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.Foundry" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,150 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Azure.AI.Agents.Persistent;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Create a python code file using the code interpreter tool with a code ready to determine the values in the Fibonacci sequence that are less then the value of 101";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(deploymentName, tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, azureAgentClient);
var thread = new AzureAIAgentThread(azureAgentClient);
// SK Azure AI Agent provides the code interpreter content and the assistant message as different contents in the call iteration.
await foreach (var content in agent.InvokeAsync(userInput, thread))
{
if (!string.IsNullOrWhiteSpace(content.Message.Content))
{
bool isCode = content.Message.Metadata?.ContainsKey(AzureAIAgent.CodeInterpreterMetadataKey) ?? false;
Console.WriteLine($"\n# {content.Message.Role}{(isCode ? "\n# Generated Code:\n" : ":")}{content.Message.Content}");
}
// Check for the citations
foreach (var item in content.Message.Items)
{
// Process each item in the message
if (item is AnnotationContent annotation)
{
if (annotation.Kind != AnnotationKind.UrlCitation)
{
Console.WriteLine($" [{item.GetType().Name}] {annotation.Label}: File #{annotation.ReferenceId}");
}
}
else if (item is FileReferenceContent fileReference)
{
Console.WriteLine($" [{item.GetType().Name}] File #{fileReference.FileId}");
}
}
}
// Clean up
await thread.DeleteAsync();
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(deploymentName, tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent skAgent = new(definition, azureAgentClient);
var agent = skAgent.AsAIAgent();
var thread = await agent.CreateSessionAsync();
var result = await agent.RunAsync(userInput, thread);
Console.WriteLine(result);
// Extracts via breaking glass the code generated by code interpreter tool
var chatResponse = result.RawRepresentation as ChatResponse;
StringBuilder generatedCode = new();
foreach (object? updateRawRepresentation in chatResponse?.RawRepresentation as IEnumerable<object?> ?? [])
{
// To capture the code interpreter input we need to break glass all the updates raw representations, to check for the RunStepDetailsUpdate type and
// get the CodeInterpreterInput property which contains the generated code.
// Note: Similar logic would needed for each individual update if used in the agent.RunStreamingAsync streaming API to aggregate or yield the generated code.
if (updateRawRepresentation is RunStepDetailsUpdate update && update.CodeInterpreterInput is not null)
{
generatedCode.Append(update.CodeInterpreterInput);
}
}
if (!string.IsNullOrEmpty(generatedCode.ToString()))
{
Console.WriteLine($"\n# {chatResponse?.Messages[0].Role}:Generated Code:\n{generatedCode}");
}
// Update the citations
foreach (var textContent in result.Messages[0].Contents.OfType<Microsoft.Extensions.AI.TextContent>())
{
foreach (var annotation in textContent.Annotations ?? [])
{
if (annotation is CitationAnnotation citation)
{
if (citation.Url is null)
{
Console.WriteLine($" [{citation.GetType().Name}] {citation.Snippet}: File #{citation.FileId}");
}
foreach (var region in citation.AnnotatedRegions ?? [])
{
if (region is TextSpanAnnotatedRegion textSpanRegion)
{
Console.WriteLine($"\n[TextSpan Region] {textSpanRegion.StartIndex}-{textSpanRegion.EndIndex}");
}
}
}
}
}
// Clean up
if (thread is ChatClientAgentSession chatSession)
{
await azureAgentClient.Threads.DeleteThreadAsync(chatSession.ConversationId);
}
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
// Note: Code interpreter via hosted APIs requires versioned Foundry Agents.
// This sample creates a basic agent without code interpreter capabilities.
var agent = new AIProjectClient(new Uri(azureEndpoint), new AzureCliCredential())
.AsAIAgent(deploymentName, instructions: "You are a helpful assistant with code execution capabilities.");
var session = await agent.CreateSessionAsync();
var result = await agent.RunAsync(userInput, session);
Console.WriteLine(result);
// No cleanup needed - non-hosted path doesn't create server-side resources.
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgent();
await SKAgent_As_AFAgentAsync();
await AFAgent();
async Task SKAgent()
{
Console.WriteLine("\n=== SK Agent ===\n");
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
var agent = new ChatCompletionAgent()
{
Kernel = builder.Build(),
Name = "Joker",
Instructions = "You are good at telling jokes.",
};
var thread = new ChatHistoryAgentThread();
var settings = new OpenAIPromptExecutionSettings() { MaxTokens = 1000 };
var agentOptions = new AgentInvokeOptions() { KernelArguments = new(settings) };
await foreach (var result in agent.InvokeAsync(userInput, thread, agentOptions))
{
Console.WriteLine(result.Message);
}
Console.WriteLine("---");
await foreach (var update in agent.InvokeStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update.Message);
}
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var agent = new ChatCompletionAgent()
{
Kernel = builder.Build(),
Name = "Joker",
Instructions = "You are good at telling jokes.",
}.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
async Task AFAgent()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName)
.AsAIAgent(name: "Joker", instructions: "You are good at telling jokes.");
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "What is the weather like in Amsterdam?";
Console.WriteLine($"User Input: {userInput}");
[KernelFunction]
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
await SKAgent();
await SKAgent_As_AFAgentAsync();
await AFAgent();
async Task SKAgent()
{
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
ChatCompletionAgent agent = new()
{
Instructions = "You are a helpful assistant",
Kernel = builder.Build(),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
Console.WriteLine("\n=== SK Agent Response ===\n");
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
ChatCompletionAgent agent = new()
{
Instructions = "You are a helpful assistant",
Kernel = builder.Build(),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
var afAgent = agent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var result = await afAgent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgent()
{
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
Console.WriteLine("\n=== AF Agent Response ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgent();
await SKAgent_As_AFAgentAsync();
await AFAgent();
async Task SKAgent()
{
Console.WriteLine("\n=== SK Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddKernel().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
serviceCollection.AddTransient((sp) => new ChatCompletionAgent()
{
Kernel = sp.GetRequiredService<Kernel>(),
Name = "Joker",
Instructions = "You are good at telling jokes."
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<ChatCompletionAgent>();
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddKernel().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential());
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
serviceCollection.AddTransient((sp) => new ChatCompletionAgent()
{
Kernel = sp.GetRequiredService<Kernel>(),
Name = "Joker",
Instructions = "You are good at telling jokes."
}.AsAIAgent());
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgent()
{
Console.WriteLine("\n=== AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<AIAgent>((sp) => new AzureOpenAIClient(new(endpoint), new AzureCliCredential())
.GetChatClient(deploymentName)
.AsAIAgent(name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Functions\Functions.OpenApi\Functions.OpenApi.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="OpenAPISpec.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,354 @@
{
"openapi": "3.0.1",
"info": {
"title": "Github Versions API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.github.com"
}
],
"components": {
"schemas": {
"basic-error": {
"title": "Basic Error",
"description": "Basic Error",
"type": "object",
"properties": {
"message": {
"type": "string"
},
"documentation_url": {
"type": "string"
},
"url": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"label": {
"title": "Label",
"description": "Color-coded labels help you categorize and filter your issues (just like labels in Gmail).",
"type": "object",
"properties": {
"id": {
"description": "Unique identifier for the label.",
"type": "integer",
"format": "int64",
"example": 208045946
},
"node_id": {
"type": "string",
"example": "MDU6TGFiZWwyMDgwNDU5NDY="
},
"url": {
"description": "URL for the label",
"example": "https://api.github.com/repositories/42/labels/bug",
"type": "string",
"format": "uri"
},
"name": {
"description": "The name of the label.",
"example": "bug",
"type": "string"
},
"description": {
"description": "Optional description of the label, such as its purpose.",
"type": "string",
"example": "Something isn't working",
"nullable": true
},
"color": {
"description": "6-character hex code, without the leading #, identifying the color",
"example": "FFFFFF",
"type": "string"
},
"default": {
"description": "Whether this label comes by default in a new repository.",
"type": "boolean",
"example": true
}
},
"required": [
"id",
"node_id",
"url",
"name",
"description",
"color",
"default"
]
},
"tag": {
"title": "Tag",
"description": "Tag",
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "v0.1"
},
"commit": {
"type": "object",
"properties": {
"sha": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
}
},
"required": [
"sha",
"url"
]
},
"zipball_url": {
"type": "string",
"format": "uri",
"example": "https://github.com/octocat/Hello-World/zipball/v0.1"
},
"tarball_url": {
"type": "string",
"format": "uri",
"example": "https://github.com/octocat/Hello-World/tarball/v0.1"
},
"node_id": {
"type": "string"
}
},
"required": [
"name",
"node_id",
"commit",
"zipball_url",
"tarball_url"
]
}
},
"examples": {
"label-items": {
"value": [
{
"id": 208045946,
"node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"description": "Something isn't working",
"color": "f29513",
"default": true
},
{
"id": 208045947,
"node_id": "MDU6TGFiZWwyMDgwNDU5NDc=",
"url": "https://api.github.com/repos/octocat/Hello-World/labels/enhancement",
"name": "enhancement",
"description": "New feature or request",
"color": "a2eeef",
"default": false
}
]
},
"tag-items": {
"value": [
{
"name": "v0.1",
"commit": {
"sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc",
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
},
"zipball_url": "https://github.com/octocat/Hello-World/zipball/v0.1",
"tarball_url": "https://github.com/octocat/Hello-World/tarball/v0.1",
"node_id": "MDQ6VXNlcjE="
}
]
}
},
"parameters": {
"owner": {
"name": "owner",
"description": "The account owner of the repository. The name is not case sensitive.",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
"repo": {
"name": "repo",
"description": "The name of the repository without the `.git` extension. The name is not case sensitive.",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
"per-page": {
"name": "per_page",
"description": "The number of results per page (max 100). For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
"in": "query",
"schema": {
"type": "integer",
"default": 30
}
},
"page": {
"name": "page",
"description": "The page number of the results to fetch. For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
"in": "query",
"schema": {
"type": "integer",
"default": 1
}
}
},
"responses": {
"not_found": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/basic-error"
}
}
}
}
},
"headers": {
"link": {
"example": "<https://api.github.com/resource?page=2>; rel=\"next\", <https://api.github.com/resource?page=5>; rel=\"last\"",
"schema": {
"type": "string"
}
}
}
},
"paths": {
"/repos/{owner}/{repo}/tags": {
"get": {
"summary": "List repository tags",
"description": "",
"tags": [
"repos"
],
"operationId": "repos/list-tags",
"externalDocs": {
"description": "API method documentation",
"url": "https://docs.github.com/rest/repos/repos#list-repository-tags"
},
"parameters": [
{
"$ref": "#/components/parameters/owner"
},
{
"$ref": "#/components/parameters/repo"
},
{
"$ref": "#/components/parameters/per-page"
},
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/tag"
}
},
"examples": {
"default": {
"$ref": "#/components/examples/tag-items"
}
}
}
},
"headers": {
"Link": {
"$ref": "#/components/headers/link"
}
}
}
},
"x-github": {
"githubCloudOnly": false,
"enabledForGitHubApps": true,
"category": "repos",
"subcategory": "repos"
}
}
},
"/repos/{owner}/{repo}/labels": {
"get": {
"summary": "List labels for a repository",
"description": "Lists all labels for a repository.",
"tags": [
"issues"
],
"operationId": "issues/list-labels-for-repo",
"externalDocs": {
"description": "API method documentation",
"url": "https://docs.github.com/rest/issues/labels#list-labels-for-a-repository"
},
"parameters": [
{
"$ref": "#/components/parameters/owner"
},
{
"$ref": "#/components/parameters/repo"
},
{
"$ref": "#/components/parameters/per-page"
},
{
"$ref": "#/components/parameters/page"
}
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/label"
}
},
"examples": {
"default": {
"$ref": "#/components/examples/label-items"
}
}
}
},
"headers": {
"Link": {
"$ref": "#/components/headers/link"
}
}
},
"404": {
"$ref": "#/components/responses/not_found"
}
},
"x-github": {
"githubCloudOnly": false,
"enabledForGitHubApps": true,
"category": "issues",
"subcategory": "labels"
}
}
}
}
}
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an Agent with function tools provided via an OpenAPI spec with both Semantic Kernel and Agent Framework.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
await SKAgent();
await AFAgent();
async Task SKAgent()
{
Console.WriteLine("\n=== SK Agent ===\n");
// Create a kernel with an Azure OpenAI chat client.
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential()).Build();
// Load the OpenAPI Spec from a file.
var plugin = await kernel.ImportPluginFromOpenApiAsync("github", "OpenAPISpec.json");
// Create the agent, and provide the kernel with the OpenAPI function tools to the agent.
var agent = new ChatCompletionAgent()
{
Kernel = kernel,
Instructions = "You are a helpful assistant",
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Run the agent with the OpenAPI function tools.
await foreach (var result in agent.InvokeAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github."))
{
Console.WriteLine(result.Message);
}
}
async Task AFAgent()
{
Console.WriteLine("\n=== AF Agent ===\n");
// Load the OpenAPI Spec from a file.
KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("github", "OpenAPISpec.json");
// Convert the Semantic Kernel plugin to Agent Framework function tools.
// This requires a dummy Kernel instance, since KernelFunctions cannot execute without one.
Kernel kernel = new();
List<AITool> tools = plugin.Select(x => x.WithKernel(kernel)).Cast<AITool>().ToList();
// Create the chat client and agent, and provide the OpenAPI function tools to the agent.
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new AzureCliCredential())
.GetChatClient(deploymentName)
.AsAIAgent(instructions: "You are a helpful assistant", tools: tools);
// Run the agent with the OpenAPI function tools.
Console.WriteLine(await agent.RunAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github."));
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient, deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
};
var agentOptions = new OpenAIResponseAgentInvokeOptions() { ResponseCreationOptions = new() { MaxOutputTokenCount = 1000 } };
Microsoft.SemanticKernel.Agents.AgentThread? thread = null;
await foreach (var item in agent.InvokeAsync(userInput, thread, agentOptions))
{
thread = item.Thread;
Console.WriteLine(item.Message);
}
Console.WriteLine("---");
await foreach (var item in agent.InvokeStreamingAsync(userInput, thread, agentOptions))
{
thread = item.Thread;
Console.Write(item.Message);
}
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient();
OpenAIResponseAgent skAgent = new(responseClient, deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
};
var agent = skAgent.AsAIAgent();
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 8000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient()
.AsAIAgent(model: deploymentName, name: "Joker", instructions: "You are good at telling jokes.");
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 8000 });
var result = await agent.RunAsync(userInput, session, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
Console.Write(update);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,225 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "o4-mini";
var userInput =
"""
Instructions:
- Given the React component below, think about it and 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>
);
}
""";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient, deploymentName)
{
Name = "Thinker",
Instructions = "You are good at thinking hard before answering.",
StoreEnabled = true
};
var agentOptions = new OpenAIResponseAgentInvokeOptions()
{
ResponseCreationOptions = new()
{
MaxOutputTokenCount = 8000,
ReasoningOptions = new()
{
ReasoningEffortLevel = OpenAI.Responses.ResponseReasoningEffortLevel.High,
ReasoningSummaryVerbosity = OpenAI.Responses.ResponseReasoningSummaryVerbosity.Detailed
}
}
};
Microsoft.SemanticKernel.Agents.AgentThread? thread = null;
await foreach (var item in agent.InvokeAsync(userInput, thread, agentOptions))
{
thread = item.Thread;
foreach (var content in item.Message.Items)
{
if (content is ReasoningContent thinking)
{
Console.Write($"Thinking: \n{thinking}\n---\n");
}
else if (content is Microsoft.SemanticKernel.TextContent text)
{
Console.Write($"Assistant: {text}");
}
}
Console.WriteLine(item.Message);
}
Console.WriteLine("---");
var userMessage = new ChatMessageContent(AuthorRole.User, userInput);
await foreach (var item in agent.InvokeStreamingAsync(userMessage, thread, agentOptions))
{
thread = item.Thread;
foreach (var content in item.Message.Items)
{
// Currently SK Agent doesn't output thinking in streaming mode.
// SK Issue: https://github.com/microsoft/semantic-kernel/issues/13046
// OpenAI SDK Issue: https://github.com/openai/openai-dotnet/issues/643
if (content is StreamingReasoningContent thinking)
{
Console.WriteLine($"Thinking: [{thinking}]");
continue;
}
if (content is StreamingTextContent text)
{
Console.WriteLine($"Response: [{text}]");
}
}
}
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient();
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
OpenAIResponseAgent skAgent = new(responseClient, deploymentName)
{
Name = "Thinker",
Instructions = "You are good at thinking hard before answering.",
StoreEnabled = true
};
var agent = skAgent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new()
{
MaxOutputTokens = 8000,
Reasoning = new()
{
Effort = ReasoningEffort.High,
Output = ReasoningOutput.Full
}
});
var result = await agent.RunAsync(userInput, thread, agentOptions);
// Retrieve the thinking as a full text block requires flattening multiple TextReasoningContents from multiple messages content lists.
string assistantThinking = string.Join("\n", result.Messages
.SelectMany(m => m.Contents)
.OfType<TextReasoningContent>()
.Select(trc => trc.Text));
var assistantText = result.Text;
Console.WriteLine($"Thinking: \n{assistantThinking}\n---\n");
Console.WriteLine($"Assistant: \n{assistantText}\n---\n");
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
var thinkingContents = update.Contents
.OfType<TextReasoningContent>()
.Select(trc => trc.Text)
.ToList();
if (thinkingContents.Count != 0)
{
Console.WriteLine($"Thinking: [{string.Join("\n", thinkingContents)}]");
continue;
}
Console.WriteLine($"Response: [{update.Text}]");
}
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient()
.AsAIAgent(model: deploymentName, name: "Thinker", instructions: "You are good at thinking hard before answering.");
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new()
{
MaxOutputTokens = 8000,
Reasoning = new()
{
Effort = ReasoningEffort.High,
Output = ReasoningOutput.Full
}
});
var result = await agent.RunAsync(userInput, session, agentOptions);
// Retrieve the thinking as a full text block requires flattening multiple TextReasoningContents from multiple messages content lists.
string assistantThinking = string.Join("\n", result.Messages
.SelectMany(m => m.Contents)
.OfType<TextReasoningContent>()
.Select(trc => trc.Text));
var assistantText = result.Text;
Console.WriteLine($"Thinking: \n{assistantThinking}\n---\n");
Console.WriteLine($"Assistant: \n{assistantText}\n---\n");
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
var thinkingContents = update.Contents
.OfType<TextReasoningContent>()
.Select(trc => trc.Text)
.ToList();
if (thinkingContents.Count != 0)
{
Console.WriteLine($"Thinking: [{string.Join("\n", thinkingContents)}]");
continue;
}
Console.WriteLine($"Response: [{update.Text}]");
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "What is the weather like in Amsterdam?";
Console.WriteLine($"User Input: {userInput}");
[KernelFunction]
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
OpenAIResponseAgent agent = new(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(), deploymentName)
{ StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
Console.WriteLine("\n=== SK Agent Response ===\n");
await foreach (ChatMessageContent responseItem in agent.InvokeAsync(userInput))
{
if (!string.IsNullOrWhiteSpace(responseItem.Content))
{
Console.WriteLine(responseItem);
}
}
}
async Task SKAgent_As_AFAgentAsync()
{
OpenAIResponseAgent skAgent = new(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(), deploymentName)
{ StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
skAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
var agent = skAgent.AsAIAgent();
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgentAsync()
{
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient()
.AsAIAgent(model: deploymentName, instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
Console.WriteLine("\n=== AF Agent Response ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(), deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<Microsoft.SemanticKernel.Agents.Agent>();
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(), deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var skAgent = serviceProvider.GetRequiredService<Microsoft.SemanticKernel.Agents.Agent>();
var agent = (skAgent as OpenAIResponseAgent)!.AsAIAgent();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<AIAgent>((sp) => new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient()
.AsAIAgent(model: deploymentName, name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using OpenAI;
using OpenAI.Chat;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
// Example of Semantic Kernel Agent code
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
var agent = new ChatCompletionAgent()
{
Kernel = builder.Build(),
Name = "Joker",
Instructions = "You are good at telling jokes.",
};
var thread = new ChatHistoryAgentThread();
var settings = new OpenAIPromptExecutionSettings() { MaxTokens = 1000 };
var agentOptions = new AgentInvokeOptions() { KernelArguments = new(settings) };
await foreach (var result in agent.InvokeAsync(userInput, thread, agentOptions))
{
Console.WriteLine(result.Message);
}
Console.WriteLine("---");
await foreach (var update in agent.InvokeStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update.Message);
}
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var agent = new ChatCompletionAgent()
{
Kernel = builder.Build(),
Name = "Joker",
Instructions = "You are good at telling jokes.",
}.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
// Example of a fully migrated Agent Framework Agent code
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new OpenAIClient(apiKey).GetChatClient(model)
.AsAIAgent(name: "Joker", instructions: "You are good at telling jokes.");
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using OpenAI;
using OpenAI.Chat;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "What is the weather like in Amsterdam?";
Console.WriteLine($"User Input: {userInput}");
[KernelFunction]
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
ChatCompletionAgent agent = new()
{
Instructions = "You are a helpful assistant",
Kernel = builder.Build(),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
Console.WriteLine("\n=== SK Agent Response ===\n");
await foreach (var item in agent.InvokeAsync(userInput))
{
Console.Write(item.Message);
}
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
ChatCompletionAgent skAgent = new()
{
Instructions = "You are a helpful assistant",
Kernel = builder.Build(),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
skAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var agent = skAgent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
Console.WriteLine("\n---\n");
await foreach (var item in skAgent.InvokeAsync(userInput))
{
Console.Write(item.Message);
}
}
async Task AFAgentAsync()
{
var agent = new OpenAIClient(apiKey).GetChatClient(model).AsAIAgent(
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);
Console.WriteLine("\n=== AF Agent Response ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using OpenAI;
using OpenAI.Chat;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddKernel().AddOpenAIChatClient(model, apiKey);
serviceCollection.AddTransient((sp) => new ChatCompletionAgent()
{
Kernel = sp.GetRequiredService<Kernel>(),
Name = "Joker",
Instructions = "You are good at telling jokes."
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<ChatCompletionAgent>();
await foreach (var item in agent.InvokeAsync(userInput))
{
Console.WriteLine(item.Message);
}
}
// Example of Semantic Kernel Agent code converted as an Agent Framework Agent
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddKernel().AddOpenAIChatClient(model, apiKey);
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
serviceCollection.AddTransient<AIAgent>((sp) => new ChatCompletionAgent()
{
Kernel = sp.GetRequiredService<Kernel>(),
Name = "Joker",
Instructions = "You are good at telling jokes."
}.AsAIAgent());
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<AIAgent>((sp) => new OpenAIClient(apiKey)
.GetChatClient(model)
.AsAIAgent(name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
};
var agentOptions = new OpenAIResponseAgentInvokeOptions() { ResponseCreationOptions = new() { MaxOutputTokenCount = 1000 } };
Microsoft.SemanticKernel.Agents.AgentThread? thread = null;
await foreach (var item in agent.InvokeAsync(userInput, thread, agentOptions))
{
Console.WriteLine(item.Message);
thread = item.Thread;
}
Console.WriteLine("---");
await foreach (var item in agent.InvokeStreamingAsync(userInput, thread, agentOptions))
{
// Thread need to be updated for subsequent calls
thread = item.Thread;
Console.Write(item.Message);
}
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent skAgent = new(responseClient)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
StoreEnabled = true
};
var agent = skAgent.AsAIAgent();
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 8000 });
var result = await agent.RunAsync(userInput, thread, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
Console.Write(update);
}
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new OpenAIClient(apiKey).GetResponsesClient()
.AsAIAgent(model: model, name: "Joker", instructions: "You are good at telling jokes.");
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 8000 });
var result = await agent.RunAsync(userInput, session, agentOptions);
Console.WriteLine(result);
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
Console.Write(update);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,222 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "o4-mini";
var userInput =
"""
Instructions:
- Given the React component below, think about it and 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>
);
}
""";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient)
{
Name = "Thinker",
Instructions = "You are good at thinking hard before answering.",
StoreEnabled = true
};
var agentOptions = new OpenAIResponseAgentInvokeOptions()
{
ResponseCreationOptions = new()
{
MaxOutputTokenCount = 8000,
ReasoningOptions = new()
{
ReasoningEffortLevel = OpenAI.Responses.ResponseReasoningEffortLevel.High,
ReasoningSummaryVerbosity = OpenAI.Responses.ResponseReasoningSummaryVerbosity.Detailed
}
}
};
Microsoft.SemanticKernel.Agents.AgentThread? thread = null;
await foreach (var item in agent.InvokeAsync(userInput, thread, agentOptions))
{
thread = item.Thread;
foreach (var content in item.Message.Items)
{
if (content is ReasoningContent thinking)
{
Console.Write($"Thinking: \n{thinking}\n---\n");
}
else if (content is Microsoft.SemanticKernel.TextContent text)
{
Console.Write($"Assistant: {text}");
}
}
Console.WriteLine(item.Message);
}
Console.WriteLine("---");
var userMessage = new ChatMessageContent(AuthorRole.User, userInput);
thread = null;
await foreach (var item in agent.InvokeStreamingAsync(userMessage, thread, agentOptions))
{
thread = item.Thread;
foreach (var content in item.Message.Items)
{
// Currently SK Agent doesn't output thinking in streaming mode.
// SK Issue: https://github.com/microsoft/semantic-kernel/issues/13046
// OpenAI SDK Issue: https://github.com/openai/openai-dotnet/issues/643
if (content is StreamingReasoningContent thinking)
{
Console.WriteLine($"Thinking: [{thinking}]");
continue;
}
if (content is StreamingTextContent text)
{
Console.WriteLine($"Response: [{text}]");
}
}
}
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
OpenAIResponseAgent skAgent = new(responseClient)
{
Name = "Thinker",
Instructions = "You are at thinking hard before answering.",
StoreEnabled = true
};
var agent = skAgent.AsAIAgent();
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var thread = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new()
{
MaxOutputTokens = 8000,
Reasoning = new()
{
Effort = ReasoningEffort.High,
Output = ReasoningOutput.Full
}
});
var result = await agent.RunAsync(userInput, thread, agentOptions);
// Retrieve the thinking as a full text block requires flattening multiple TextReasoningContents from multiple messages content lists.
string assistantThinking = string.Join("\n", result.Messages
.SelectMany(m => m.Contents)
.OfType<TextReasoningContent>()
.Select(trc => trc.Text));
var assistantText = result.Text;
Console.WriteLine($"Thinking: \n{assistantThinking}\n---\n");
Console.WriteLine($"Assistant: \n{assistantText}\n---\n");
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
{
var thinkingContents = update.Contents
.OfType<TextReasoningContent>()
.Select(trc => trc.Text)
.ToList();
if (thinkingContents.Count != 0)
{
Console.WriteLine($"Thinking: [{string.Join("\n", thinkingContents)}]");
continue;
}
Console.WriteLine($"Response: [{update.Text}]");
}
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new OpenAIClient(apiKey).GetResponsesClient()
.AsAIAgent(model: model, name: "Thinker", instructions: "You are at thinking hard before answering.");
var session = await agent.CreateSessionAsync();
var agentOptions = new ChatClientAgentRunOptions(new()
{
MaxOutputTokens = 8000,
Reasoning = new()
{
Effort = ReasoningEffort.High,
Output = ReasoningOutput.Full
}
});
var result = await agent.RunAsync(userInput, session, agentOptions);
// Retrieve the thinking as a full text block requires flattening multiple TextReasoningContents from multiple messages content lists.
string assistantThinking = string.Join("\n", result.Messages
.SelectMany(m => m.Contents)
.OfType<TextReasoningContent>()
.Select(trc => trc.Text));
var assistantText = result.Text;
Console.WriteLine($"Thinking: \n{assistantThinking}\n---\n");
Console.WriteLine($"Assistant: \n{assistantText}\n---\n");
Console.WriteLine("---");
await foreach (var update in agent.RunStreamingAsync(userInput, session, agentOptions))
{
var thinkingContents = update.Contents
.OfType<TextReasoningContent>()
.Select(trc => trc.Text)
.ToList();
if (thinkingContents.Count != 0)
{
Console.WriteLine($"Thinking: [{string.Join("\n", thinkingContents)}]");
continue;
}
Console.WriteLine($"Response: [{update.Text}]");
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "What is the weather like in Amsterdam?";
Console.WriteLine($"User Input: {userInput}");
[KernelFunction]
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
OpenAIResponseAgent agent = new(new OpenAIClient(apiKey).GetResponsesClient(), model) { StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
Console.WriteLine("\n=== SK Agent Response ===\n");
await foreach (ChatMessageContent responseItem in agent.InvokeAsync(userInput))
{
if (!string.IsNullOrWhiteSpace(responseItem.Content))
{
Console.WriteLine(responseItem);
}
}
}
async Task SKAgent_As_AFAgentAsync()
{
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
OpenAIResponseAgent skAgent = new(new OpenAIClient(apiKey).GetResponsesClient(), model) { StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
skAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
var agent = skAgent.AsAIAgent();
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgentAsync()
{
var agent = new OpenAIClient(apiKey).GetResponsesClient().AsAIAgent(
model: model,
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);
Console.WriteLine("\n=== AF Agent Response ===\n");
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = System.Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o";
var userInput = "Tell me a joke about a pirate.";
Console.WriteLine($"User Input: {userInput}");
await SKAgentAsync();
await SKAgent_As_AFAgentAsync();
await AFAgentAsync();
async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(), model)
{
Name = "Joker",
Instructions = "You are good at telling jokes."
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<Microsoft.SemanticKernel.Agents.Agent>();
var result = await agent.InvokeAsync(userInput).FirstAsync();
Console.WriteLine(result.Message);
}
async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(), model)
{
Name = "Joker",
Instructions = "You are good at telling jokes."
});
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var skAgent = serviceProvider.GetRequiredService<Microsoft.SemanticKernel.Agents.Agent>();
var agent = (skAgent as OpenAIResponseAgent)!.AsAIAgent();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient((sp) => new OpenAIClient(apiKey)
.GetResponsesClient()
.AsAIAgent(model: model, name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
var result = await agent.RunAsync(userInput);
Console.WriteLine(result);
}
@@ -0,0 +1,20 @@
# Semantic Kernel Migration Playground
This is a playground folder with different **Semantic Kernel** projects that can be used to test automatic AI migration to the new **Agent Framework (AF)**.
## Prompting
Open your IDE Agentic extension and create a new chat providing the following prompt:
```
I need to convert code from Semantic Kernel to the Agent Framework.
Please use the migration guide provided in the #SemanticKernelToAgentFramework.md as a reference.
The current solution is using central package manager, when referencing the projects in the csproj don't provide the versions.
Check external references provided by the migration guide if needed.
You don't need to look for the Central Package Management file, just focus on the project file and the code files.
When you need help or don't know how to proceed, please ask.
```
@@ -0,0 +1,89 @@
// 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.ChatCompletion;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var client = new PersistentAgentsClient(Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT"), new AzureCliCredential());
// Define the agent
PersistentAgent definition = await client.Administration.CreateAgentAsync(
Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME"),
instructions: "You are a coding assistant that always generates code using the code interpreter tool.",
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, client);
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(client);
try
{
await InvokeAgentAsync("Create a python file where it determines the values in the Fibonacci sequence that that are less then the value of 101.");
}
finally
{
await thread.DeleteAsync();
await client.Administration.DeleteAgentAsync(agent.Id);
}
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
WriteAgentChatMessage(response);
}
}
void WriteAgentChatMessage(ChatMessageContent message)
{
// Include ChatMessageContent.AuthorName in output, if present.
string authorExpression = message.Role == AuthorRole.User ? string.Empty : FormatAuthor();
// Include TextContent (via ChatMessageContent.Content), if present.
string contentExpression = string.IsNullOrWhiteSpace(message.Content) ? string.Empty : message.Content;
bool isCode = message.Metadata?.ContainsKey(AzureAIAgent.CodeInterpreterMetadataKey) ?? false;
string codeMarker = isCode ? "\n [CODE]\n" : " ";
Console.WriteLine($"\n# {message.Role}{authorExpression}:{codeMarker}{contentExpression}");
// Provide visibility for inner content (that isn't TextContent).
foreach (KernelContent item in message.Items)
{
if (item is AnnotationContent annotation)
{
if (annotation.Kind == AnnotationKind.UrlCitation)
{
Console.WriteLine($" [{item.GetType().Name}] {annotation.Label}: {annotation.ReferenceId} - {annotation.Title}");
}
else
{
Console.WriteLine($" [{item.GetType().Name}] {annotation.Label}: File #{annotation.ReferenceId}");
}
}
else if (item is ActionContent action)
{
Console.WriteLine($" [{item.GetType().Name}] {action.Text}");
}
else if (item is ReasoningContent reasoning)
{
Console.WriteLine($" [{item.GetType().Name}] {reasoning.Text ?? "Thinking..."}");
}
else if (item is FileReferenceContent fileReference)
{
Console.WriteLine($" [{item.GetType().Name}] File #{fileReference.FileId}");
}
}
if ((message.Metadata?.TryGetValue("Usage", out object? usage) ?? false) && usage is RunStepCompletionUsage agentUsage)
{
Console.WriteLine($" [Usage] Tokens: {agentUsage.TotalTokens}, Input: {agentUsage.PromptTokens}, Output: {agentUsage.CompletionTokens}");
}
string FormatAuthor() => message.AuthorName is not null ? $" - {message.AuthorName ?? " * "}" : string.Empty;
}
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1707;CA2007;VSTHRD111</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Agents\AzureAI\Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Agents\Core\Agents.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="../../../../../.github/upgrades/prompts/*" />
</ItemGroup>
</Project>
@@ -0,0 +1,405 @@
# Semantic Kernel to Agent Framework Migration Guide
## What's Changed?
- **Namespace Updates**: From `Microsoft.SemanticKernel.Agents` to `Microsoft.Agents.AI`
- **Agent Creation**: Single fluent API calls vs multi-step builder patterns
- **Thread Management**: Built-in thread management vs manual thread creation
- **Tool Registration**: Direct function registration vs plugin wrapper systems
- **Dependency Injection**: Simplified service registration patterns
- **Invocation Patterns**: Streamlined options and result handling
## Benefits of Migration
- **Simplified API**: Reduced complexity and boilerplate code
- **Better Performance**: Optimized object creation and memory usage
- **Unified Interface**: Consistent patterns across different AI providers
- **Enhanced Developer Experience**: More intuitive and discoverable APIs
## Key Changes
### 1. Namespace Updates
#### Semantic Kernel
```csharp
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
```
#### Agent Framework
Agent Framework namespaces are under `Microsoft.Agents.AI`.
Agent Framework uses the core AI message and content types from `Microsoft.Extensions.AI` for communication between components.
```csharp
using Microsoft.Extensions.AI;
using Microsoft.Agents.AI;
```
### 2. Agent Creation Simplification
#### Semantic Kernel
Every agent in Semantic Kernel depends on a `Kernel` instance and will have
an empty `Kernel` if not provided.
```csharp
Kernel kernel = Kernel
.AddOpenAIChatClient(modelId, apiKey)
.Build();
ChatCompletionAgent agent = new() { Instructions = ParrotInstructions, Kernel = kernel };
```
Azure AI Foundry requires an agent resource to be created in the cloud before creating a local agent class that uses it.
```csharp
PersistentAgentsClient azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(
deploymentName,
instructions: ParrotInstructions);
AzureAIAgent agent = new(definition, azureAgentClient);
```
#### Agent Framework
Agent creation in Agent Framework is made simpler with extensions provided by all main providers.
```csharp
AIAgent openAIAgent = chatClient.CreateAIAgent(instructions: ParrotInstructions);
AIAgent azureFoundryAgent = await persistentAgentsClient.CreateAIAgentAsync(instructions: ParrotInstructions);
AIAgent openAIAssistantAgent = await assistantClient.CreateAIAgentAsync(instructions: ParrotInstructions);
```
Additionally for hosted agent providers you can also use the `GetAIAgent` to retrieve an agent from an existing hosted agent.
```csharp
AIAgent azureFoundryAgent = await persistentAgentsClient.GetAIAgentAsync(agentId);
```
### 3. Agent Thread Creation
#### Semantic Kernel
The caller has to know the thread type and create it manually.
```csharp
// Create a thread for the agent conversation.
AgentThread thread = new OpenAIAssistantAgentThread(this.AssistantClient);
AgentThread thread = new AzureAIAgentThread(this.Client);
AgentThread thread = new OpenAIResponseAgentThread(this.Client);
```
#### Agent Framework
The agent is responsible for creating the thread.
```csharp
// New
AgentThread thread = agent.GetNewThread();
```
### 4. Hosted Agent Thread Cleanup
This case applies exclusively to a few AI providers that still provide hosted threads.
#### Semantic Kernel
Threads have a `self` deletion method
i.e: OpenAI Assistants Provider
```csharp
await thread.DeleteAsync();
```
#### Agent Framework
> [!NOTE]
> OpenAI Responses introduced a new conversation model that simplifies how conversations are handled. This simplifies hosted thread management compared to the now deprecated OpenAI Assistants model. For more information see the [OpenAI Assistants migration guide](https://platform.openai.com/docs/assistants/migration).
Agent Framework doesn't have a thread deletion API in the `AgentThread` type as not all providers support hosted threads or thread deletion and this will become more common as more providers shift to responses based architectures.
If you require thread deletion and the provider allows this, the caller **should** keep track of the created threads and delete them later when necessary via the provider's sdk.
i.e: OpenAI Assistants Provider
```csharp
await assistantClient.DeleteThreadAsync(thread.ConversationId);
```
### 5. Tool Registration
#### Semantic Kernel
In semantic kernel to expose a function as a tool you must:
1. Decorate the function with a `[KernelFunction]` attribute.
2. Have a `Plugin` class or use the `KernelPluginFactory` to wrap the function.
3. Have a `Kernel` to add your plugin to.
4. Pass the `Kernel` to the agent.
```csharp
KernelFunction function = KernelFunctionFactory.CreateFromMethod(GetWeather);
KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("KernelPluginName", [function]);
Kernel kernel = ... // Create kernel
kernel.Plugins.Add(plugin);
ChatCompletionAgent agent = new() { Kernel = kernel, ... };
```
#### Agent Framework
In agent framework in a single call you can register tools directly in the agent creation process.
```csharp
AIAgent agent = chatClient.CreateAIAgent(tools: [AIFunctionFactory.Create(GetWeather)]);
```
### 6. Agent Non-Streaming Invocation
Key differences can be seen in the method names from `Invoke` to `Run`, return types and parameters `AgentRunOptions`.
#### Semantic Kernel
The Non-Streaming uses a streaming pattern `IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>` for returning multiple agent messages.
```csharp
await foreach (AgentResponseItem<ChatMessageContent> result in agent.InvokeAsync(userInput, thread, agentOptions))
{
Console.WriteLine(result.Message);
}
```
#### Agent Framework
The Non-Streaming returns a single `AgentRunResponse` with the agent response that can contain multiple messages.
The text result of the run is available in `AgentRunResponse.Text` or `AgentRunResponse.ToString()`.
All messages created as part of the response is returned in the `AgentRunResponse.Messages` list.
This may include tool call messages, function results, reasoning updates and final results.
```csharp
AgentRunResponse agentResponse = await agent.RunAsync(userInput, thread);
```
### 7. Agent Streaming Invocation
Key differences in the method names from `Invoke` to `Run`, return types and parameters `AgentRunOptions`.
#### Semantic Kernel
```csharp
await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(userInput, thread))
{
Console.Write(update);
}
```
#### Agent Framework
Similar streaming API pattern with the key difference being that it returns `AgentRunResponseUpdate` objects including more agent related information per update.
All updates produced by any service underlying the AIAgent is returned. The textual result of the agent is available by concatenating the `AgentRunResponse.Text` values.
```csharp
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(userInput, thread))
{
Console.Write(update); // Update is ToString() friendly
}
```
### 8. Tool Function Signatures
**Problem**: SK plugin methods need `[KernelFunction]` attributes
```csharp
public class MenuPlugin
{
[KernelFunction] // Required for SK
public static MenuItem[] GetMenu() => ...;
}
```
**Solution**: AF can use methods directly without attributes
```csharp
public class MenuTools
{
[Description("Get menu items")] // Optional description
public static MenuItem[] GetMenu() => ...;
}
```
### 9. Options Configuration
**Problem**: Complex options setup in SK
```csharp
OpenAIPromptExecutionSettings settings = new() { MaxTokens = 1000 };
AgentInvokeOptions options = new() { KernelArguments = new(settings) };
```
**Solution**: Simplified options in AF
```csharp
ChatClientAgentRunOptions options = new(new() { MaxOutputTokens = 1000 });
```
> [!IMPORTANT]
> This example shows passing implementation specific options to a `ChatClientAgent`. Not all `AIAgents` support `ChatClientAgentRunOptions`.
> `ChatClientAgent` is provided to build agents based on underlying inference services, and therefore supports inference options like `MaxOutputTokens`.
### 10. Dependency Injection
#### Semantic Kernel
A `Kernel` registration is required in the service container to be able to create an agent
as every agent abstractions needs to be initialized with a `Kernel` property.
Semantic Kernel uses the `Agent` type as the base abstraction class for agents.
```csharp
services.AddKernel().AddProvider(...);
serviceContainer.AddKeyedSingleton<SemanticKernel.Agents.Agent>(
TutorName,
(sp, key) =>
new ChatCompletionAgent()
{
// Passing the kernel is required
Kernel = sp.GetRequiredService<Kernel>(),
});
```
### 11. **Agent Type Consolidation**
#### Semantic Kernel
Semantic kernel provides specific agent classes for various services, e.g.
- `ChatCompletionAgent` for use with chat-completion-based inference services.
- `OpenAIAssistantAgent` for use with the OpenAI Assistants service.
- `AzureAIAgent` for use with the Azure AI Foundry Agents service.
#### Agent Framework
The agent framework supports all the abovementioned services via a single agent type, `ChatClientAgent`.
`ChatClientAgent` can be used to build agents using any underlying service that provides an SDK implementing the `Microsoft.Extensions.AI.IChatClient` interface.
#### Agent Framework
The Agent framework provides the `AIAgent` type as the base abstraction class.
```csharp
services.AddKeyedSingleton<AIAgent>(() => client.CreateAIAgent(...));
```
## Migration Samples
This folder contains **separate console application projects** demonstrating how to transition from **Semantic Kernel (SK)** to the new **Agent Framework (AF)**.
Each project shows side-by-side comparisons of equivalent functionality in both frameworks and can be run independently.
Each sample code contains the following:
1. **SK Agent** (Semantic Kernel before)
2. **AF Agent** (Agent Framework after)
### Running the samples from Visual Studio
Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
You will be prompted for any required environment variables if they are not already set.
### Prerequisites
Before you begin, ensure you have the following:
- [.NET 10.0 SDK or later](https://dotnet.microsoft.com/download)
- For Azure AI Foundry samples: Azure OpenAI service endpoint and deployment configured
- For OpenAI samples: OpenAI API key
- For OpenAI Assistants samples: OpenAI API key with Assistant API access
### Environment Variables
Set the appropriate environment variables based on the sample type you want to run:
**For Azure AI Foundry projects:**
```powershell
$env:AZURE_FOUNDRY_PROJECT_ENDPOINT = "https://<your-project>-resource.services.ai.azure.com/api/projects/<your-project>"
```
**For OpenAI and OpenAI Assistants projects:**
```powershell
$env:OPENAI_API_KEY = "sk-..."
```
**For Azure OpenAI and Azure OpenAI Assistants projects:**
```powershell
$env:AZURE_OPENAI_ENDPOINT = "https://<your-project>.cognitiveservices.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o" # Optional, defaults to gpt-4o
```
**Optional debug mode:**
```powershell
$env:AF_SHOW_ALL_DEMO_SETTING_VALUES = "Y"
```
If environment variables are not set, the demos will prompt you to enter values interactively.
### Samples
The migration samples are organized into different categories, each demonstrating different AI service integrations and orchestration patterns:
|Category|Description|
|---|---|
|[AzureAIFoundry](./AzureAIFoundry/)|Azure OpenAI service integration samples|
|[AzureOpenAI](./AzureOpenAI/)|Direct Azure OpenAI API integration samples|
|[AzureOpenAIAssistants](./AzureOpenAIAssistants/)|Azure OpenAI Assistants API integration samples|
|[AzureOpenAIResponses](./AzureOpenAIResponses/)|Azure OpenAI Responses API integration samples|
|[OpenAI](./OpenAI/)|Direct OpenAI API integration samples|
|[OpenAIAssistants](./OpenAIAssistants/)|OpenAI Assistants API integration samples|
|[OpenAIResponses](./OpenAIResponses/)|OpenAI Responses API integration samples|
|[AgentOrchestrations](./AgentOrchestrations/)|Agent orchestration patterns including concurrent, sequential, and handoff workflows|
## Running the samples from the console
To run any migration sample, navigate to the desired sample directory:
```powershell
# Azure AI Foundry Examples
cd "AzureAIFoundry\Step01_Basics"
dotnet run
# Azure OpenAI Examples
cd "AzureOpenAI\Step01_Basics"
dotnet run
# Azure OpenAI Assistants Examples
cd "AzureOpenAIAssistants\Step01_Basics"
dotnet run
# Azure OpenAI Responses Examples
cd "AzureOpenAIResponses\Step01_Basics"
dotnet run
# OpenAI Examples
cd "OpenAI\Step01_Basics"
dotnet run
# OpenAI Assistants Examples
cd "OpenAIAssistants\Step01_Basics"
dotnet run
# OpenAI Responses Examples
cd "OpenAIResponses\Step01_Basics"
dotnet run
# Agent Orchestrations Examples
cd "AgentOrchestrations\Step01_Concurrent"
dotnet run
cd "AgentOrchestrations\Step02_Sequential"
dotnet run
cd "AgentOrchestrations\Step03_Handoff"
dotnet run
```
@@ -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 Agents;
/// <summary>
/// Demonstrate using code-interpreter to manipulate and generate csv files with <see cref="AzureAIAgent"/> .
/// </summary>
public class AzureAIAgent_FileManipulation(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task AnalyzeCSVFileUsingAzureAIAgentAsync()
{
await using Stream stream = EmbeddedResource.ReadStream("sales.csv")!;
PersistentAgentFileInfo fileInfo = await this.Client.Files.UploadFileAsync(stream, PersistentAgentFilePurpose.Agents, "sales.csv");
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new CodeInterpreterToolDefinition()],
toolResources:
new()
{
CodeInterpreter = new()
{
FileIds = { fileInfo.Id },
}
});
AzureAIAgent agent = new(definition, this.Client);
AzureAIAgentThread thread = new(this.Client);
// Respond to user input
try
{
await InvokeAgentAsync("Which segment had the most sales?");
await InvokeAgentAsync("List the top 5 countries that generated the most profit.");
await InvokeAgentAsync("Create a tab delimited file report of profit by each country per month.");
}
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(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);
await this.DownloadContentAsync(response);
}
}
}
}
@@ -0,0 +1,207 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate consuming "streaming" message for <see cref="AzureAIAgent"/>.
/// </summary>
public class AzureAIAgent_Streaming(ITestOutputHelper output) : BaseAzureAgentTest(output)
{
[Fact]
public async Task UseStreamingAgentAsync()
{
const string AgentName = "Parrot";
const string AgentInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions);
AzureAIAgent agent = new(definition, this.Client);
try
{
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Fortune favors the bold.");
await InvokeAgentAsync(agent, agentThread, "I came, I saw, I conquered.");
await InvokeAgentAsync(agent, agentThread, "Practice makes perfect.");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
public async Task UseStreamingAssistantAgentWithPluginAsync()
{
const string AgentName = "Host";
const string AgentInstructions = "Answer questions about the menu.";
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions);
AzureAIAgent agent = new(definition, this.Client);
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
try
{
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, agentThread, "What is the special drink and its price?");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Threads.DeleteThreadAsync(agentThread.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
public async Task UseStreamingAssistantWithCodeInterpreterAsync()
{
const string AgentName = "MathGuy";
const string AgentInstructions = "Solve math problems with code.";
// Define the agent
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions,
[new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
try
{
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Is 191 a prime number?");
await InvokeAgentAsync(agent, agentThread, "Determine the values in the Fibonacci sequence that that are less then the value of 101");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Threads.DeleteThreadAsync(agentThread.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(AzureAIAgent agent, Microsoft.SemanticKernel.Agents.AgentThread agentThread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
// For this sample, also capture fully formed messages so we can display them later.
ChatHistory history = [];
Task OnNewMessage(ChatMessageContent message)
{
history.Add(message);
return Task.CompletedTask;
}
bool isFirst = false;
bool isCode = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread, new AgentInvokeOptions() { OnIntermediateMessage = OnNewMessage }))
{
if (string.IsNullOrEmpty(response.Content))
{
StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType<StreamingFunctionCallUpdateContent>().SingleOrDefault();
if (functionCall?.Name != null)
{
(string? pluginName, string functionName) = this.ParseFunctionName(functionCall.Name);
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {$"{pluginName}." ?? string.Empty}{functionName}");
}
continue;
}
// Differentiate between assistant and tool messages
if (isCode != (response.Metadata?.ContainsKey(AzureAIAgent.CodeInterpreterMetadataKey) ?? false))
{
isFirst = false;
isCode = !isCode;
}
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
foreach (ChatMessageContent content in history)
{
this.WriteAgentChatMessage(content);
}
}
private async Task DisplayChatHistoryAsync(AzureAIAgentThread agentThread)
{
Console.WriteLine("================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
ChatMessageContent[] messages = await agentThread.GetMessagesAsync().ToArrayAsync();
for (int index = messages.Length - 1; index >= 0; --index)
{
this.WriteAgentChatMessage(messages[index]);
}
}
public sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials()
{
return
"""
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
""";
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice(
[Description("The name of the menu item.")]
string menuItem)
{
return "$9.99";
}
}
}
@@ -0,0 +1,223 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.Functions;
namespace Agents;
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
/// <summary>
/// Demonstrates the creation of a <see cref="ChatCompletionAgent"/> and adding capabilities
/// for contextual function selection to it. Contextual function selection involves using
/// Retrieval-Augmented Generation (RAG) to identify and select the most relevant functions
/// based on the current context. The provider vectorizes the function names and descriptions,
/// stores them in a specified vector store, and performs a vector search to find and provide
/// the most pertinent functions to the AI model/agent for a given context.
/// </summary>
public class ChatCompletion_ContextualFunctionSelection(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Shows how to configure agent to use <see cref="ContextualFunctionProvider"/>
/// to enable contextual function selection based on the current invocation context.
/// </summary>
[Fact]
private async Task SelectFunctionsRelevantToCurrentInvocationContext()
{
var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
.GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
.AsIEmbeddingGenerator(1536);
// Create our agent.
Kernel kernel = this.CreateKernelWithChatCompletion();
ChatCompletionAgent agent =
new()
{
Name = "ReviewGuru",
Instructions = "You are a friendly assistant that summarizes key points and sentiments from customer reviews. " +
"For each response, list available functions",
Kernel = kernel,
Arguments = new(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new FunctionChoiceBehaviorOptions { RetainArgumentTypes = true }) })
};
// Create a thread and register context based function selection provider that will do RAG on
// provided functions to advertise only those that are relevant to the current context.
ChatHistoryAgentThread agentThread = new();
var allAvailableFunctions = GetAvailableFunctions();
agentThread.AIContextProviders.Add(
new ContextualFunctionProvider(
vectorStore: new InMemoryVectorStore(new InMemoryVectorStoreOptions() { EmbeddingGenerator = embeddingGenerator }),
vectorDimensions: 1536,
functions: allAvailableFunctions,
maxNumberOfFunctions: 3, // Instruct the provider to return a maximum of 3 relevant functions
loggerFactory: this.LoggerFactory
)
);
// Invoke and display assistant response
ChatMessageContent message = await agent.InvokeAsync("Get and summarize customer review.", agentThread).FirstAsync();
Console.WriteLine(message.Content);
//Expected output:
/*
Retrieves and summarizes customer reviews.
### Customer Reviews:
1. **John D.** - ★★★★★
*Comment:* Great product and fast shipping!
*Date:* 2023-10-01
2. **Jane S.** - ★★★★
*Comment:* Good quality, but delivery was a bit slow.
*Date:* 2023-09-28
3. **Mike J.** - ★★★
*Comment:* Average. Works as expected.
*Date:* 2023-09-25
### Summary:
The reviews indicate overall customer satisfaction, with highlights on product quality and shipping efficiency.
While some customers experienced excellent service, others mentioned areas for improvement, particularly regarding delivery times.
If you need further analysis or insights, feel free to ask!
Available functions:
- Tools-GetCustomerReviews
- Tools-Summarize
- Tools-CollectSentiments
*/
}
/// <summary>
/// Shows how to configure agent to use <see cref="ContextualFunctionProvider"/>
/// to enable contextual function selection based on the previous and current invocation context.
/// </summary>
[Fact]
private async Task SelectFunctionsBasedOnPreviousAndCurrentInvocationContext()
{
var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
.GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
.AsIEmbeddingGenerator(1536);
// Create our agent.
Kernel kernel = this.CreateKernelWithChatCompletion();
ChatCompletionAgent agent =
new()
{
Name = "AzureAssistant",
Instructions = "You are a helpful assistant that helps with Azure resource management. " +
"Avoid including the phrase like 'If you need further assistance or have any additional tasks, feel free to let me know!' in any responses.",
Kernel = kernel,
Arguments = new(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new FunctionChoiceBehaviorOptions { RetainArgumentTypes = true }) })
};
// Create a thread and register context based function selection provider that will do RAG on
// provided functions to advertise only those that are relevant to the current context.
ChatHistoryAgentThread agentThread = new();
var allAvailableFunctions = GetAvailableFunctions();
agentThread.AIContextProviders.Add(
new ContextualFunctionProvider(
vectorStore: new InMemoryVectorStore(new InMemoryVectorStoreOptions() { EmbeddingGenerator = embeddingGenerator }),
vectorDimensions: 1536,
functions: allAvailableFunctions,
maxNumberOfFunctions: 1, // Instruct the provider to return only one relevant function
loggerFactory: this.LoggerFactory,
options: new ContextualFunctionProviderOptions
{
NumberOfRecentMessagesInContext = 1 // Use only the last message from the previous agent invocation
}
)
);
// Ask agent to provision a VM on Azure. The contextual function selection provider will return only one relevant function: `ProvisionVM`
ChatMessageContent message = await agent.InvokeAsync("Please provision a VM on Azure", agentThread).FirstAsync();
Console.WriteLine(message.Content);
//Expected output: "A virtual machine has been successfully provisioned on Azure with the ID: 7f2aa1e4-13ac-4875-9e63-278ee82f3729."
// Ask the agent to deploy the VM, intentionally referring to the VM as "it".
// This demonstrates that the contextual function selection provider uses the last message from the previous invocation
// to infer that the user is referring to the VM provisioned in the invocation and not any other Azure resource.
// The provider will return only one relevant function to deploy the VM: `DeployVM`
message = await agent.InvokeAsync("Deploy it", agentThread).FirstAsync();
Console.WriteLine(message.Content);
//Expected output: "The virtual machine with ID: 7f2aa1e4-13ac-4875-9e63-278ee82f3729 has been successfully deployed."
}
/// <summary>
/// Returns a list of functions that belong to different categories.
/// Some categories/functions are related to the prompt, while others
/// are not. This is intentionally done to demonstrate the contextual
/// function selection capabilities of the provider.
/// </summary>
private IReadOnlyList<AIFunction> GetAvailableFunctions()
{
List<AIFunction> reviewFunctions = [
AIFunctionFactory.Create(() => """
[
{
"reviewer": "John D.",
"date": "2023-10-01",
"rating": 5,
"comment": "Great product and fast shipping!"
},
{
"reviewer": "Jane S.",
"date": "2023-09-28",
"rating": 4,
"comment": "Good quality, but delivery was a bit slow."
},
{
"reviewer": "Mike J.",
"date": "2023-09-25",
"rating": 3,
"comment": "Average. Works as expected."
}
]
"""
, "GetCustomerReviews"),
];
List<AIFunction> sentimentFunctions = [
AIFunctionFactory.Create((string text) => "The collected sentiment is mostly positive with a few neutral and negative opinions.", "CollectSentiments"),
AIFunctionFactory.Create((string text) => "Sentiment trend identified: predominantly positive with increasing positive feedback.", "IdentifySentimentTrend"),
];
List<AIFunction> summaryFunctions = [
AIFunctionFactory.Create((string text) => "Summary generated based on input data: key points include market growth and customer satisfaction.", "Summarize"),
AIFunctionFactory.Create((string text) => "Extracted themes: innovation, efficiency, customer satisfaction.", "ExtractThemes"),
];
List<AIFunction> communicationFunctions = [
AIFunctionFactory.Create((string address, string content) => "Email sent.", "SendEmail"),
AIFunctionFactory.Create((string number, string text) => "Message sent.", "SendSms"),
AIFunctionFactory.Create(() => "user@domain.com", "MyEmail"),
];
List<AIFunction> dateTimeFunctions = [
AIFunctionFactory.Create(() => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "GetCurrentDateTime"),
AIFunctionFactory.Create(() => DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"), "GetCurrentUtcDateTime"),
];
List<AIFunction> azureFunctions = [
AIFunctionFactory.Create(() => $"Resource group provisioned: Id:{Guid.NewGuid()}", "ProvisionResourceGroup"),
AIFunctionFactory.Create((Guid id) => $"Resource group deployed: Id:{id}", "DeployResourceGroup"),
AIFunctionFactory.Create(() => $"Storage account provisioned: Id:{Guid.NewGuid()}", "ProvisionStorageAccount"),
AIFunctionFactory.Create((Guid id) => $"Storage account deployed: Id:{id}", "DeployStorageAccount"),
AIFunctionFactory.Create(() => $"VM provisioned: Id:{Guid.NewGuid()}", "ProvisionVM"),
AIFunctionFactory.Create((Guid id) => $"VM deployed: Id:{id}", "DeployVM"),
];
return [.. reviewFunctions, .. sentimentFunctions, .. summaryFunctions, .. communicationFunctions, .. dateTimeFunctions, .. azureFunctions];
}
}
@@ -0,0 +1,286 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate usage of <see cref="IAutoFunctionInvocationFilter"/> for both direction invocation
/// of <see cref="ChatCompletionAgent"/> and via <see cref="AgentChat"/>.
/// </summary>
public class ChatCompletion_FunctionTermination(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseAutoFunctionInvocationFilterWithAgentInvocation(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Instructions = "Answer questions about the menu.",
Kernel = CreateKernelWithFilter(useChatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
/// Create the thread to capture the agent interaction.
ChatHistoryAgentThread agentThread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup?");
await InvokeAgentAsync("What is the special drink?");
await InvokeAgentAsync("Thank you");
// Display the entire chat history.
WriteChatHistory(await agentThread.GetMessagesAsync().ToArrayAsync());
// 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, agentThread))
{
this.WriteAgentChatMessage(response);
}
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseAutoFunctionInvocationFilterWithAgentChat(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Instructions = "Answer questions about the menu.",
Kernel = CreateKernelWithFilter(useChatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
// Create a chat for agent interaction.
AgentGroupChat chat = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup?");
await InvokeAgentAsync("What is the special drink?");
await InvokeAgentAsync("Thank you");
// Display the entire chat history.
WriteChatHistory(await chat.GetChatMessagesAsync().ToArrayAsync());
// 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);
}
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseAutoFunctionInvocationFilterWithStreamingAgentInvocation(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Instructions = "Answer questions about the menu.",
Kernel = CreateKernelWithFilter(useChatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
/// Create the thread to capture the agent interaction.
ChatHistoryAgentThread agentThread = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup?");
await InvokeAgentAsync("What is the special drink?");
await InvokeAgentAsync("Thank you");
// Display the entire chat history.
WriteChatHistory(await agentThread.GetMessagesAsync().ToArrayAsync());
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
int historyCount = agentThread.ChatHistory.Count;
bool isFirst = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
if (string.IsNullOrEmpty(response.Content))
{
continue;
}
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
if (historyCount <= agentThread.ChatHistory.Count)
{
for (int index = historyCount; index < agentThread.ChatHistory.Count; index++)
{
this.WriteAgentChatMessage(agentThread.ChatHistory[index]);
}
}
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseAutoFunctionInvocationFilterWithStreamingAgentChat(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Instructions = "Answer questions about the menu.",
Kernel = CreateKernelWithFilter(useChatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
// Create a chat for agent interaction.
AgentGroupChat chat = new();
// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync("Hello");
await InvokeAgentAsync("What is the special soup?");
await InvokeAgentAsync("What is the special drink?");
await InvokeAgentAsync("Thank you");
// Display the entire chat history.
WriteChatHistory(await chat.GetChatMessagesAsync().ToArrayAsync());
// 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);
bool isFirst = false;
await foreach (StreamingChatMessageContent response in chat.InvokeStreamingAsync(agent))
{
if (string.IsNullOrEmpty(response.Content))
{
continue;
}
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
}
}
private void WriteChatHistory(IEnumerable<ChatMessageContent> chat)
{
Console.WriteLine("================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
foreach (ChatMessageContent message in chat)
{
this.WriteAgentChatMessage(message);
}
}
private Kernel CreateKernelWithFilter(bool useChatClient)
{
IKernelBuilder builder = Kernel.CreateBuilder();
if (useChatClient)
{
base.AddChatClientToKernel(builder);
}
else
{
base.AddChatCompletionToKernel(builder);
}
builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new AutoInvocationFilter());
return builder.Build();
}
private sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials()
{
return
"""
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
""";
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice(
[Description("The name of the menu item.")]
string menuItem)
{
return "$9.99";
}
}
private sealed class AutoInvocationFilter(bool terminate = true) : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
// Execution the function
await next(context);
// Signal termination if the function is from the MenuPlugin
if (context.Function.PluginName == nameof(MenuPlugin))
{
context.Terminate = terminate;
}
}
}
}
@@ -0,0 +1,117 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// eliciting its response to three explicit user messages.
/// </summary>
public class ChatCompletion_HistoryReducer(ITestOutputHelper output) : BaseTest(output)
{
private const string TranslatorName = "NumeroTranslator";
private const string TranslatorInstructions = "Add one to latest user number and spell it in spanish without explanation.";
/// <summary>
/// Demonstrate the use of <see cref="ChatHistoryTruncationReducer"/> when directly
/// invoking a <see cref="ChatCompletionAgent"/>.
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task TruncatedAgentReduction(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateTruncatingAgent(10, 10, useChatClient, out var chatClient);
await InvokeAgentAsync(agent, 50);
chatClient?.Dispose();
}
/// <summary>
/// Demonstrate the use of <see cref="ChatHistorySummarizationReducer"/> when directly
/// invoking a <see cref="ChatCompletionAgent"/>.
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task SummarizedAgentReduction(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateSummarizingAgent(10, 10, useChatClient, out var chatClient);
await InvokeAgentAsync(agent, 50);
chatClient?.Dispose();
}
// Proceed with dialog by directly invoking the agent and explicitly managing the history.
private async Task InvokeAgentAsync(ChatCompletionAgent agent, int messageCount)
{
ChatHistoryAgentThread agentThread = new();
int index = 1;
while (index <= messageCount)
{
// Provide user input
Console.WriteLine($"# {AuthorRole.User}: '{index}'");
// Reduce prior to invoking the agent
bool isReduced = await agent.ReduceAsync(agentThread.ChatHistory);
// Invoke and display assistant response
await foreach (ChatMessageContent message in agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, $"{index}"), agentThread))
{
Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'");
}
index += 2;
// Display the message count of the chat-history for visibility into reduction
Console.WriteLine($"@ Message Count: {agentThread.ChatHistory.Count}\n");
// Display summary messages (if present) if reduction has occurred
if (isReduced)
{
int summaryIndex = 0;
while (agentThread.ChatHistory[summaryIndex].Metadata?.ContainsKey(ChatHistorySummarizationReducer.SummaryMetadataKey) ?? false)
{
Console.WriteLine($"\tSummary: {agentThread.ChatHistory[summaryIndex].Content}");
++summaryIndex;
}
}
}
}
private ChatCompletionAgent CreateSummarizingAgent(int reducerMessageCount, int reducerThresholdCount, bool useChatClient, out IChatClient? chatClient)
{
Kernel kernel = this.CreateKernelWithChatCompletion(useChatClient, out chatClient);
var service = useChatClient
? kernel.GetRequiredService<IChatClient>().AsChatCompletionService()
: kernel.GetRequiredService<IChatCompletionService>();
return
new()
{
Name = TranslatorName,
Instructions = TranslatorInstructions,
Kernel = kernel,
HistoryReducer = new ChatHistorySummarizationReducer(service, reducerMessageCount, reducerThresholdCount),
};
}
private ChatCompletionAgent CreateTruncatingAgent(int reducerMessageCount, int reducerThresholdCount, bool useChatClient, out IChatClient? chatClient) =>
new()
{
Name = TranslatorName,
Instructions = TranslatorInstructions,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out chatClient),
HistoryReducer = new ChatHistoryTruncationReducer(reducerMessageCount, reducerThresholdCount),
};
}
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Net.Http.Headers;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Memory;
namespace Agents;
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// adding memory capabilities to it using https://mem0.ai/.
/// </summary>
public class ChatCompletion_Mem0(ITestOutputHelper output) : BaseTest(output)
{
private const string AgentName = "FriendlyAssistant";
private const string AgentInstructions = "You are a friendly assistant";
/// <summary>
/// Shows how to allow an agent to remember user preferences across multiple threads.
/// </summary>
[Fact]
private async Task UseMemoryAsync()
{
// Create a new HttpClient with the base address of the mem0 service and a token for authentication.
using var httpClient = new HttpClient()
{
BaseAddress = new Uri(TestConfiguration.Mem0.BaseAddress ?? "https://api.mem0.ai")
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", TestConfiguration.Mem0.ApiKey);
// Create a mem0 component with the current user's id, so that it stores memories for that user.
var mem0Provider = new Mem0Provider(httpClient, options: new()
{
UserId = "U1"
});
// Clear out any memories from previous runs, if any, to demonstrate a first run experience.
await mem0Provider.ClearStoredMemoriesAsync();
// Create our agent and add our finance plugin with auto function invocation.
Kernel kernel = this.CreateKernelWithChatCompletion();
kernel.Plugins.AddFromType<FinancePlugin>();
ChatCompletionAgent agent =
new()
{
Name = AgentName,
Instructions = AgentInstructions,
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
};
Console.WriteLine("----- First Conversation -----");
// Create a thread for the agent and add the mem0 component to it.
ChatHistoryAgentThread agentThread = new();
agentThread.AIContextProviders.Add(mem0Provider);
// First ask the agent to retrieve a company report with no previous context.
// The agent will not be able to invoke the plugin, since it doesn't know
// the company code or the report format, so it should ask for clarification.
string userMessage = "Please retrieve my company report";
Console.WriteLine($"User: {userMessage}");
ChatMessageContent message = await agent.InvokeAsync(userMessage, agentThread).FirstAsync();
Console.WriteLine($"Assistant:\n{message.Content}");
// Now tell the agent the company code and the report format that you want to use
// and it should be able to invoke the plugin and return the report.
userMessage = "I always work with CNTS and I always want a detailed report format";
Console.WriteLine($"User: {userMessage}");
message = await agent.InvokeAsync(userMessage, agentThread).FirstAsync();
Console.WriteLine($"Assistant:\n{message.Content}");
Console.WriteLine("----- Second Conversation -----");
// Create a new thread for the agent and add our mem0 component to it again
// The new thread has no context of the previous conversation.
agentThread = new();
agentThread.AIContextProviders.Add(mem0Provider);
// Since we have the mem0 component in the thread, the agent should be able to
// retrieve the company report without asking for clarification, as it will
// be able to remember the user preferences from the last thread.
userMessage = "Please retrieve my company report";
Console.WriteLine($"User: {userMessage}");
message = await agent.InvokeAsync(userMessage, agentThread).FirstAsync();
Console.WriteLine($"Assistant:\n{message.Content}");
}
private sealed class FinancePlugin
{
[KernelFunction]
public string RetrieveCompanyReport(string companyCode, ReportFormat reportFormat)
{
if (companyCode != "CNTS")
{
throw new ArgumentException("Company code not found");
}
return reportFormat switch
{
ReportFormat.Brief => "CNTS is a company that specializes in technology.",
ReportFormat.Detailed => "CNTS is a company that specializes in technology. It had a revenue of $10 million in 2022. It has 100 employees.",
_ => throw new ArgumentException("Report format not found")
};
}
}
private enum ReportFormat
{
Brief,
Detailed
}
}
@@ -0,0 +1,183 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.Data;
namespace Agents;
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// adding simple retrieval augmented generation (RAG) capabilities to it.
/// </summary>
/// <remarks>
/// This example shows how to use the <see cref="TextSearchStore{TKey}"/> class which is designed
/// to simplify the process of storing and searching text documents by having a built in schema.
/// If you want to control the schema yourself, you can use an implementation of <see cref="VectorStoreCollection{TKey, TRecord}"/>
/// with the <see cref="VectorStoreTextSearch{TRecord}"/> class instead.
/// </remarks>
public class ChatCompletion_Rag(ITestOutputHelper output) : BaseTest(output)
{
private const string AgentName = "FriendlyAssistant";
private const string AgentInstructions = "You are a friendly assistant";
/// <summary>
/// Shows how to do Retrieval Augmented Generation (RAG) with some basic text strings.
/// </summary>
[Fact]
private async Task UseChatCompletionAgentWithBasicRag()
{
var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
.GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
.AsIEmbeddingGenerator(1536);
// Create a vector store to store our documents.
// Note that the embedding generator provided here must be able to generate embeddings matching the
// number of dimensions configured for the TextSearchStore below.
var vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
// Create a store that uses a built in schema for storing text documents
// and provides easy upload and search capabilities.
// The data is stored in the `FinancialData` collection and embeddings have 1536 dimensions.
// When searching results will be limited to those with the `group/g2` namespace.
using var textSearchStore = new TextSearchStore<string>(vectorStore, collectionName: "FinancialData", vectorDimensions: 1536);
// Upsert documents into the store.
await textSearchStore.UpsertTextAsync(
[
"The financial results of Contoso Corp for 2024 is as follows:\nIncome EUR 154 000 000\nExpenses EUR 142 000 000",
"The financial results of Contoso Corp for 2023 is as follows:\nIncome EUR 174 000 000\nExpenses EUR 152 000 000",
"The financial results of Contoso Corp for 2022 is as follows:\nIncome EUR 184 000 000\nExpenses EUR 162 000 000",
"The Contoso Corporation is a multinational business with its headquarters in Paris. The company is a manufacturing, sales, and support organization with more than 100,000 products.",
"The financial results of AdventureWorks for 2021 is as follows:\nIncome USD 223 000 000\nExpenses USD 210 000 000",
"AdventureWorks is a large American business that specializes in adventure parks and family entertainment.",
]);
// Create our agent.
Kernel kernel = this.CreateKernelWithChatCompletion();
ChatCompletionAgent agent =
new()
{
Name = AgentName,
Instructions = AgentInstructions,
Kernel = kernel,
};
// Create a thread for the agent.
ChatHistoryAgentThread agentThread = new();
// Create a text search provider that can automatically search the vector store
// for documents that match the user's query and inject them into the agent's prompt.
var textSearchProvider = new TextSearchProvider(textSearchStore);
agentThread.AIContextProviders.Add(textSearchProvider);
// Invoke and display assistant response
ChatMessageContent message = await agent.InvokeAsync("Where is Contoso based?", agentThread).FirstAsync();
Console.WriteLine(message.Content);
message = await agent.InvokeAsync("What was its expenses for 2022?", agentThread).FirstAsync();
Console.WriteLine(message.Content);
}
/// <summary>
/// Shows how to do Retrieval Augmented Generation (RAG) with citations and filtering.
/// </summary>
[Fact]
private async Task RagWithCitationsAndFiltering()
{
var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
.GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
.AsIEmbeddingGenerator(1536);
// Create a vector store to store our documents.
// Note that the embedding generator provided here must be able to generate embeddings matching the
// number of dimensions configured for the TextSearchStore below.
var vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
// Create a store that uses a built in schema for storing text documents
// and provides easy upload and search capabilities.
// The data is stored in the `FinancialData` collection and embeddings have 1536 dimensions.
// When searching results will be limited to those with the `group/g2` namespace.
using var textSearchStore = new TextSearchStore<string>(vectorStore, collectionName: "FinancialData", vectorDimensions: 1536, new() { SearchNamespace = "group/g2" });
// Upsert documents into the store.
// Not that documents have different namespaces, and only the ones
// with the `group/g2` namespace will be matched.
await textSearchStore.UpsertDocumentsAsync(GetSampleDocuments());
// Create our agent.
Kernel kernel = this.CreateKernelWithChatCompletion();
ChatCompletionAgent agent =
new()
{
Name = AgentName,
Instructions = AgentInstructions,
Kernel = kernel,
};
// Create a thread for the agent.
ChatHistoryAgentThread agentThread = new();
// Create a text search provider that can automatically search the vector store
// for documents that match the user's query and inject them into the agent's prompt.
var textSearchProvider = new TextSearchProvider(textSearchStore);
agentThread.AIContextProviders.Add(textSearchProvider);
// Invoke and display assistant response
ChatMessageContent message = await agent.InvokeAsync("What was the income of Contoso for 2023", agentThread).FirstAsync();
Console.WriteLine(message.Content);
}
private static IEnumerable<TextSearchDocument> GetSampleDocuments()
{
yield return new TextSearchDocument
{
Text = "The financial results of Contoso Corp for 2024 is as follows:\nIncome EUR 154 000 000\nExpenses EUR 142 000 000",
SourceName = "Contoso 2024 Financial Report",
SourceLink = "https://www.consoso.com/reports/2024.pdf",
Namespaces = ["group/g1"]
};
yield return new TextSearchDocument
{
Text = "The financial results of Contoso Corp for 2023 is as follows:\nIncome EUR 174 000 000\nExpenses EUR 152 000 000",
SourceName = "Contoso 2023 Financial Report",
SourceLink = "https://www.consoso.com/reports/2023.pdf",
Namespaces = ["group/g2"]
};
yield return new TextSearchDocument
{
Text = "The financial results of Contoso Corp for 2022 is as follows:\nIncome EUR 184 000 000\nExpenses EUR 162 000 000",
SourceName = "Contoso 2022 Financial Report",
SourceLink = "https://www.consoso.com/reports/2022.pdf",
Namespaces = ["group/g2"]
};
yield return new TextSearchDocument
{
Text = "The Contoso Corporation is a multinational business with its headquarters in Paris. The company is a manufacturing, sales, and support organization with more than 100,000 products.",
SourceName = "About Contoso",
SourceLink = "https://www.consoso.com/about-us",
Namespaces = ["group/g2"]
};
yield return new TextSearchDocument
{
Text = "The financial results of AdventureWorks for 2021 is as follows:\nIncome USD 223 000 000\nExpenses USD 210 000 000",
SourceName = "AdventureWorks 2021 Financial Report",
SourceLink = "https://www.adventure-works.com/reports/2021.pdf",
Namespaces = ["group/g1", "group/g2"]
};
yield return new TextSearchDocument
{
Text = "AdventureWorks is a large American business that specializes in adventure parks and family entertainment.",
SourceName = "About AdventureWorks",
SourceLink = "https://www.adventure-works.com/about-us",
Namespaces = ["group/g1", "group/g2"]
};
}
}
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate that serialization of <see cref="AgentGroupChat"/> in with a <see cref="ChatCompletionAgent"/> participant.
/// </summary>
public class ChatCompletion_Serialization(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string HostName = "Host";
private const string HostInstructions = "Answer questions about the menu.";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task SerializeAndRestoreAgentGroupChat(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Instructions = HostInstructions,
Name = HostName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
AgentGroupChat chat = CreateGroupChat();
// Invoke chat and display messages.
Console.WriteLine("============= Dynamic Agent Chat - Primary (prior to serialization) ==============");
await InvokeAgentAsync(chat, "Hello");
await InvokeAgentAsync(chat, "What is the special soup?");
AgentGroupChat copy = CreateGroupChat();
Console.WriteLine("\n=========== Serialize and restore the Agent Chat into a new instance ============");
await CloneChatAsync(chat, copy);
Console.WriteLine("\n============ Continue with the dynamic Agent Chat (after deserialization) ===============");
await InvokeAgentAsync(copy, "What is the special drink?");
await InvokeAgentAsync(copy, "Thank you");
Console.WriteLine("\n============ The entire Agent Chat (includes messages prior to serialization and those after deserialization) ==============");
await foreach (ChatMessageContent content in copy.GetChatMessagesAsync())
{
this.WriteAgentChatMessage(content);
}
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(AgentGroupChat chat, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent content in chat.InvokeAsync())
{
this.WriteAgentChatMessage(content);
}
}
async Task CloneChatAsync(AgentGroupChat source, AgentGroupChat clone)
{
await using MemoryStream stream = new();
await AgentChatSerializer.SerializeAsync(source, stream);
stream.Position = 0;
using StreamReader reader = new(stream);
Console.WriteLine(await reader.ReadToEndAsync());
stream.Position = 0;
AgentChatSerializer serializer = await AgentChatSerializer.DeserializeAsync(stream);
await serializer.DeserializeAsync(clone);
}
AgentGroupChat CreateGroupChat() => new(agent);
}
private sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials() =>
"""
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
""";
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice(
[Description("The name of the menu item.")]
string menuItem) =>
"$9.99";
}
}
@@ -0,0 +1,170 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate service selection for <see cref="ChatCompletionAgent"/> through setting service-id
/// on <see cref="Agent.Arguments"/> and also providing override <see cref="KernelArguments"/>
/// when calling <see cref="ChatCompletionAgent.InvokeAsync(ICollection{ChatMessageContent}, AgentThread?, AgentInvokeOptions?, CancellationToken)"/>
/// </summary>
public class ChatCompletion_ServiceSelection(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ServiceKeyGood = "chat-good";
private const string ServiceKeyBad = "chat-bad";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseServiceSelectionWithChatCompletionAgent(bool useChatClient)
{
// Create kernel with two instances of chat services - one good, one bad
Kernel kernel = CreateKernelWithTwoServices(useChatClient);
// Define the agent targeting ServiceId = ServiceKeyGood
ChatCompletionAgent agentGood =
new()
{
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { ServiceId = ServiceKeyGood }),
};
// Define the agent targeting ServiceId = ServiceKeyBad
ChatCompletionAgent agentBad =
new()
{
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings() { ServiceId = ServiceKeyBad }),
};
// Define the agent with no explicit ServiceId defined
ChatCompletionAgent agentDefault = new() { Kernel = kernel };
// Invoke agent as initialized with ServiceId = ServiceKeyGood: Expect agent response
Console.WriteLine("\n[Agent With Good ServiceId]");
await InvokeAgentAsync(agentGood);
// Invoke agent as initialized with ServiceId = ServiceKeyBad: Expect failure due to invalid service key
Console.WriteLine("\n[Agent With Bad ServiceId]");
await InvokeAgentAsync(agentBad);
// Invoke agent as initialized with no explicit ServiceId: Expect agent response
Console.WriteLine("\n[Agent With No ServiceId]");
await InvokeAgentAsync(agentDefault);
// Invoke agent with override arguments where ServiceId = ServiceKeyGood: Expect agent response
Console.WriteLine("\n[Bad Agent: Good ServiceId Override]");
await InvokeAgentAsync(agentBad, new(new PromptExecutionSettings() { ServiceId = ServiceKeyGood }));
// Invoke agent with override arguments where ServiceId = ServiceKeyBad: Expect failure due to invalid service key
Console.WriteLine("\n[Good Agent: Bad ServiceId Override]");
await InvokeAgentAsync(agentGood, new(new PromptExecutionSettings() { ServiceId = ServiceKeyBad }));
Console.WriteLine("\n[Default Agent: Bad ServiceId Override]");
await InvokeAgentAsync(agentDefault, new(new PromptExecutionSettings() { ServiceId = ServiceKeyBad }));
// Invoke agent with override arguments with no explicit ServiceId: Expect agent response
Console.WriteLine("\n[Good Agent: No ServiceId Override]");
await InvokeAgentAsync(agentGood, new(new PromptExecutionSettings()));
Console.WriteLine("\n[Bad Agent: No ServiceId Override]");
await InvokeAgentAsync(agentBad, new(new PromptExecutionSettings()));
Console.WriteLine("\n[Default Agent: No ServiceId Override]");
await InvokeAgentAsync(agentDefault, new(new PromptExecutionSettings()));
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(ChatCompletionAgent agent, KernelArguments? arguments = null)
{
try
{
await foreach (ChatMessageContent response in agent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, "Hello"),
options: new() { KernelArguments = arguments }))
{
Console.WriteLine(response.Content);
}
}
catch (HttpOperationException exception)
{
Console.WriteLine($"Status: {exception.StatusCode}");
}
catch (ClientResultException cre)
{
Console.WriteLine($"Status: {cre.Status}");
}
}
}
private Kernel CreateKernelWithTwoServices(bool useChatClient)
{
IKernelBuilder builder = Kernel.CreateBuilder();
if (useChatClient)
{
// Add chat clients
if (this.UseOpenAIConfig)
{
builder.Services.AddKeyedChatClient(
ServiceKeyBad,
new OpenAI.OpenAIClient("bad-key").GetChatClient(TestConfiguration.OpenAI.ChatModelId).AsIChatClient());
builder.Services.AddKeyedChatClient(
ServiceKeyGood,
new OpenAI.OpenAIClient(TestConfiguration.OpenAI.ApiKey).GetChatClient(TestConfiguration.OpenAI.ChatModelId).AsIChatClient());
}
else
{
builder.Services.AddKeyedChatClient(
ServiceKeyBad,
new Azure.AI.OpenAI.AzureOpenAIClient(
new Uri(TestConfiguration.AzureOpenAI.Endpoint),
new Azure.AzureKeyCredential("bad-key"))
.GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName)
.AsIChatClient());
builder.Services.AddKeyedChatClient(
ServiceKeyGood,
new Azure.AI.OpenAI.AzureOpenAIClient(
new Uri(TestConfiguration.AzureOpenAI.Endpoint),
new Azure.AzureKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))
.GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName)
.AsIChatClient());
}
}
else
{
// Add chat completion services
if (this.UseOpenAIConfig)
{
builder.AddOpenAIChatCompletion(
TestConfiguration.OpenAI.ChatModelId,
"bad-key",
serviceId: ServiceKeyBad);
builder.AddOpenAIChatCompletion(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey,
serviceId: ServiceKeyGood);
}
else
{
builder.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
"bad-key",
serviceId: ServiceKeyBad);
builder.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey,
serviceId: ServiceKeyGood);
}
}
return builder.Build();
}
}
@@ -0,0 +1,151 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Agents;
/// <summary>
/// Demonstrate consuming "streaming" message for <see cref="ChatCompletionAgent"/>.
/// </summary>
public class ChatCompletion_Streaming(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.";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseStreamingChatCompletionAgent(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = ParrotName,
Instructions = ParrotInstructions,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
ChatHistoryAgentThread agentThread = new();
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Fortune favors the bold.");
await InvokeAgentAsync(agent, agentThread, "I came, I saw, I conquered.");
await InvokeAgentAsync(agent, agentThread, "Practice makes perfect.");
// Output the entire chat history
await DisplayChatHistory(agentThread);
chatClient?.Dispose();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseStreamingChatCompletionAgentWithPlugin(bool useChatClient)
{
const string MenuInstructions = "Answer questions about the menu.";
// Define the agent
ChatCompletionAgent agent =
new()
{
Name = "Host",
Instructions = MenuInstructions,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
ChatHistoryAgentThread agentThread = new();
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "What is the special soup?");
await InvokeAgentAsync(agent, agentThread, "What is the special drink?");
// Output the entire chat history
await DisplayChatHistory(agentThread);
chatClient?.Dispose();
}
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistoryAgentThread agentThread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
int historyCount = agentThread.ChatHistory.Count;
bool isFirst = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
if (string.IsNullOrEmpty(response.Content))
{
StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType<StreamingFunctionCallUpdateContent>().SingleOrDefault();
if (!string.IsNullOrEmpty(functionCall?.Name))
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {functionCall.Name}");
}
continue;
}
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
if (historyCount <= agentThread.ChatHistory.Count)
{
for (int index = historyCount; index < agentThread.ChatHistory.Count; index++)
{
this.WriteAgentChatMessage(agentThread.ChatHistory[index]);
}
}
}
private async Task DisplayChatHistory(ChatHistoryAgentThread agentThread)
{
// Display the chat history.
Console.WriteLine("================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
await foreach (ChatMessageContent message in agentThread.GetMessagesAsync())
{
this.WriteAgentChatMessage(message);
}
}
public sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials()
{
return @"
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
";
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice(
[Description("The name of the menu item.")]
string menuItem)
{
return "$9.99";
}
}
}
@@ -0,0 +1,149 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;
namespace Agents;
/// <summary>
/// Demonstrate parameterized template instruction for <see cref="ChatCompletionAgent"/>.
/// </summary>
public class ChatCompletion_Templating(ITestOutputHelper output) : BaseAgentsTest(output)
{
private static readonly (string Input, string? Style)[] s_inputs =
[
(Input: "Home cooking is great.", Style: null),
(Input: "Talk about world peace.", Style: "iambic pentameter"),
(Input: "Say something about doing your best.", Style: "e. e. cummings"),
(Input: "What do you think about having fun?", Style: "old school rap")
];
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task InvokeAgentWithInstructionsTemplate(bool useChatClient)
{
// Instruction based template always processed by KernelPromptTemplateFactory
ChatCompletionAgent agent =
new()
{
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
Instructions =
"""
Write a one verse poem on the requested topic in the style of {{$style}}.
Always state the requested style of the poem.
""",
Arguments = new KernelArguments()
{
{"style", "haiku"}
}
};
await InvokeChatCompletionAgentWithTemplateAsync(agent);
chatClient?.Dispose();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task InvokeAgentWithKernelTemplate(bool useChatClient)
{
// Default factory is KernelPromptTemplateFactory
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{$style}}.
Always state the requested style of the poem.
""",
PromptTemplateConfig.SemanticKernelTemplateFormat,
new KernelPromptTemplateFactory(),
useChatClient);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task InvokeAgentWithHandlebarsTemplate(bool useChatClient)
{
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{style}}.
Always state the requested style of the poem.
""",
HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat,
new HandlebarsPromptTemplateFactory(),
useChatClient);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task InvokeAgentWithLiquidTemplate(bool useChatClient)
{
await InvokeChatCompletionAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the style of {{style}}.
Always state the requested style of the poem.
""",
LiquidPromptTemplateFactory.LiquidTemplateFormat,
new LiquidPromptTemplateFactory(),
useChatClient);
}
private async Task InvokeChatCompletionAgentWithTemplateAsync(
string instructionTemplate,
string templateFormat,
IPromptTemplateFactory templateFactory,
bool useChatClient)
{
// Define the agent
PromptTemplateConfig templateConfig =
new()
{
Template = instructionTemplate,
TemplateFormat = templateFormat,
};
ChatCompletionAgent agent =
new(templateConfig, templateFactory)
{
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
Arguments = new KernelArguments()
{
{"style", "haiku"}
}
};
await InvokeChatCompletionAgentWithTemplateAsync(agent);
chatClient?.Dispose();
}
private async Task InvokeChatCompletionAgentWithTemplateAsync(ChatCompletionAgent agent)
{
ChatHistory chat = [];
foreach ((string input, string? style) in s_inputs)
{
// Add input to chat
ChatMessageContent request = new(AuthorRole.User, input);
this.WriteAgentChatMessage(request);
KernelArguments? arguments = null;
if (!string.IsNullOrWhiteSpace(style))
{
// Override style template parameter
arguments = new() { { "style", style } };
}
// Process agent response
await foreach (ChatMessageContent message in agent.InvokeAsync(request, options: new() { KernelArguments = arguments }))
{
chat.Add(message);
this.WriteAgentChatMessage(message);
}
}
}
}
@@ -0,0 +1,137 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Memory;
namespace Agents;
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
/// <summary>
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and
/// adding whiteboarding capabilities, where the most relevant information from the conversation is captured on a whiteboard.
/// This is useful for long running conversations where the conversation history may need to be truncated
/// over time, but you do not want to agent to lose context.
/// </summary>
public class ChatCompletion_Whiteboard(ITestOutputHelper output) : BaseTest(output)
{
private const string AgentName = "FriendlyAssistant";
private const string AgentInstructions = "You are a friendly assistant";
/// <summary>
/// Shows how to allow an agent to use a whiteboard for storing the most important information
/// from a long running, truncated conversation.
/// </summary>
[Fact]
private async Task UseWhiteboardForShortTermMemory()
{
var chatClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new AzureCliCredential())
.GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName)
.AsIChatClient();
// Create the whiteboard.
var whiteboardProvider = new WhiteboardProvider(chatClient);
// Create our agent and add our finance plugin with auto function invocation.
Kernel kernel = this.CreateKernelWithChatCompletion();
// Create the agent with our sample plugin.
kernel.Plugins.AddFromType<VMPlugin>();
ChatCompletionAgent agent =
new()
{
Name = AgentName,
Instructions = AgentInstructions,
Kernel = kernel,
Arguments = new KernelArguments(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
};
// Create a chat history reducer that we can use to truncate the chat history
// when it goes over 3 items.
var chatHistoryReducer = new ChatHistoryTruncationReducer(3, 3);
// Create a thread for the agent and add the whiteboard to it.
ChatHistoryAgentThread agentThread = new();
agentThread.AIContextProviders.Add(whiteboardProvider);
// Simulate a conversation with the agent.
// We will also truncate the conversation once it goes over a few items.
await InvokeWithConsoleWriteLine("Hello");
await InvokeWithConsoleWriteLine("I'd like to create a VM?");
await InvokeWithConsoleWriteLine("I want it to have 3 cores.");
await InvokeWithConsoleWriteLine("I want it to have 48GB of RAM.");
await InvokeWithConsoleWriteLine("I want it to have a 500GB Harddrive.");
await InvokeWithConsoleWriteLine("I want it in Europe.");
await InvokeWithConsoleWriteLine("Can you make it Linux and call it 'ContosoVM'.");
await InvokeWithConsoleWriteLine("OK, let's call it `ContosoFinanceVM_Europe` instead.");
await InvokeWithConsoleWriteLine("Thanks, now I want to create another VM.");
await InvokeWithConsoleWriteLine("Make all the options the same as the last one, except for the region, which should be North America, and the name, which should be 'ContosoFinanceVM_NorthAmerica'.");
async Task InvokeWithConsoleWriteLine(string message)
{
// Print the user input.
Console.WriteLine($"User: {message}");
// Invoke the agent.
ChatMessageContent response = await agent.InvokeAsync(message, agentThread).FirstAsync();
// Print the response.
Console.WriteLine($"Assistant:\n{response.Content}\n");
// Make sure any async whiteboard processing is complete before we print out its contents.
await whiteboardProvider.WhenProcessingCompleteAsync();
// Print out the whiteboard contents.
Console.WriteLine("Whiteboard contents:");
foreach (var item in whiteboardProvider.CurrentWhiteboardContent)
{
Console.WriteLine($"- {item}");
}
Console.WriteLine();
// Truncate the chat history if it gets too big.
await agentThread.ChatHistory.ReduceInPlaceAsync(chatHistoryReducer, CancellationToken.None);
}
}
private sealed class VMPlugin
{
[KernelFunction]
public Task<VMCreateResult> CreateVM(Region region, OperatingSystem os, string name, int numberOfCores, int memorySizeInGB, int hddSizeInGB)
{
if (name == "ContosoVM")
{
throw new Exception("VM name already exists");
}
return Task.FromResult(new VMCreateResult { VMId = Guid.NewGuid().ToString() });
}
}
public class VMCreateResult
{
public string VMId { get; set; } = string.Empty;
}
private enum Region
{
NorthAmerica,
SouthAmerica,
Europe,
Asia,
Africa,
Australia
}
private enum OperatingSystem
{
Windows,
Linux,
MacOS
}
}
@@ -0,0 +1,232 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Resources;
using ChatResponseFormat = OpenAI.Chat.ChatResponseFormat;
namespace Agents;
/// <summary>
/// Demonstrate usage of <see cref="KernelFunctionTerminationStrategy"/> and <see cref="KernelFunctionSelectionStrategy"/>
/// to manage <see cref="AgentGroupChat"/> execution.
/// </summary>
public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string InternalLeaderName = "InternalLeader";
private const string InternalLeaderInstructions =
"""
Your job is to clearly and directly communicate the current assistant response to the user.
If information has been requested, only repeat the request.
If information is provided, only repeat the information.
Do not come up with your own shopping suggestions.
""";
private const string InternalGiftIdeaAgentName = "InternalGiftIdeas";
private const string InternalGiftIdeaAgentInstructions =
"""
You are a personal shopper that provides gift ideas.
Only provide ideas when the following is known about the gift recipient:
- Relationship to giver
- Reason for gift
Request any missing information before providing ideas.
Only describe the gift by name.
Always immediately incorporate review feedback and provide an updated response.
""";
private const string InternalGiftReviewerName = "InternalGiftReviewer";
private const string InternalGiftReviewerInstructions =
"""
Review the most recent shopping response.
Either provide critical feedback to improve the response without introducing new ideas or state that the response is adequate.
""";
private const string InnerSelectionInstructions =
$$$"""
Select which participant will take the next turn based on the conversation history.
Only choose from these participants:
- {{{InternalGiftIdeaAgentName}}}
- {{{InternalGiftReviewerName}}}
- {{{InternalLeaderName}}}
Choose the next participant according to the action of the most recent participant:
- After user input, it is {{{InternalGiftIdeaAgentName}}}'a turn.
- After {{{InternalGiftIdeaAgentName}}} replies with ideas, it is {{{InternalGiftReviewerName}}}'s turn.
- After {{{InternalGiftIdeaAgentName}}} requests additional information, it is {{{InternalLeaderName}}}'s turn.
- After {{{InternalGiftReviewerName}}} provides feedback or instruction, it is {{{InternalGiftIdeaAgentName}}}'s turn.
- After {{{InternalGiftReviewerName}}} states the {{{InternalGiftIdeaAgentName}}}'s response is adequate, it is {{{InternalLeaderName}}}'s turn.
Respond in JSON format. The JSON schema can include only:
{
"name": "string (the name of the assistant selected for the next turn)",
"reason": "string (the reason for the participant was selected)"
}
History:
{{${{{KernelFunctionSelectionStrategy.DefaultHistoryVariableName}}}}}
""";
private const string OuterTerminationInstructions =
$$$"""
Determine if user request has been fully answered.
Respond in JSON format. The JSON schema can include only:
{
"isAnswered": "bool (true if the user request has been fully answered)",
"reason": "string (the reason for your determination)"
}
History:
{{${{{KernelFunctionTerminationStrategy.DefaultHistoryVariableName}}}}}
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task NestedChatWithAggregatorAgent(bool useChatClient)
{
Console.WriteLine($"! {Model}");
OpenAIPromptExecutionSettings jsonSettings = new() { ResponseFormat = ChatResponseFormat.CreateJsonObjectFormat() };
PromptExecutionSettings autoInvokeSettings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
ChatCompletionAgent internalLeaderAgent = CreateAgent(InternalLeaderName, InternalLeaderInstructions);
ChatCompletionAgent internalGiftIdeaAgent = CreateAgent(InternalGiftIdeaAgentName, InternalGiftIdeaAgentInstructions);
ChatCompletionAgent internalGiftReviewerAgent = CreateAgent(InternalGiftReviewerName, InternalGiftReviewerInstructions);
KernelFunction innerSelectionFunction = KernelFunctionFactory.CreateFromPrompt(InnerSelectionInstructions, jsonSettings);
KernelFunction outerTerminationFunction = KernelFunctionFactory.CreateFromPrompt(OuterTerminationInstructions, jsonSettings);
AggregatorAgent personalShopperAgent =
new(CreateChat)
{
Name = "PersonalShopper",
Mode = AggregatorMode.Nested,
};
AgentGroupChat chat =
new(personalShopperAgent)
{
ExecutionSettings =
new()
{
TerminationStrategy =
new KernelFunctionTerminationStrategy(outerTerminationFunction, CreateKernelWithChatCompletion(useChatClient, out var chatClient))
{
ResultParser =
(result) =>
{
OuterTerminationResult? jsonResult = JsonResultTranslator.Translate<OuterTerminationResult>(result.GetValue<string>());
return jsonResult?.isAnswered ?? false;
},
MaximumIterations = 5,
},
}
};
// Invoke chat and display messages.
Console.WriteLine("\n######################################");
Console.WriteLine("# DYNAMIC CHAT");
Console.WriteLine("######################################");
await InvokeChatAsync("Can you provide three original birthday gift ideas. I don't want a gift that someone else will also pick.");
await InvokeChatAsync("The gift is for my adult brother.");
if (!chat.IsComplete)
{
await InvokeChatAsync("He likes photography.");
}
Console.WriteLine("\n\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
Console.WriteLine(">>>> AGGREGATED CHAT");
Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
await foreach (ChatMessageContent message in chat.GetChatMessagesAsync(personalShopperAgent).Reverse())
{
this.WriteAgentChatMessage(message);
}
chatClient?.Dispose();
async Task InvokeChatAsync(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
await foreach (ChatMessageContent response in chat.InvokeAsync(personalShopperAgent))
{
this.WriteAgentChatMessage(response);
}
Console.WriteLine($"\n# IS COMPLETE: {chat.IsComplete}");
}
ChatCompletionAgent CreateAgent(string agentName, string agentInstructions) =>
new()
{
Instructions = agentInstructions,
Name = agentName,
Kernel = this.CreateKernelWithChatCompletion(),
};
AgentGroupChat CreateChat() =>
new(internalLeaderAgent, internalGiftReviewerAgent, internalGiftIdeaAgent)
{
ExecutionSettings =
new()
{
SelectionStrategy =
new KernelFunctionSelectionStrategy(innerSelectionFunction, CreateKernelWithChatCompletion())
{
ResultParser =
(result) =>
{
AgentSelectionResult? jsonResult = JsonResultTranslator.Translate<AgentSelectionResult>(result.GetValue<string>());
string? agentName = string.IsNullOrWhiteSpace(jsonResult?.name) ? null : jsonResult?.name;
agentName ??= InternalGiftIdeaAgentName;
Console.WriteLine($"\t>>>> INNER TURN: {agentName}");
return agentName;
}
},
TerminationStrategy =
new AgentTerminationStrategy()
{
Agents = [internalLeaderAgent],
MaximumIterations = 7,
AutomaticReset = true,
},
}
};
}
private sealed record OuterTerminationResult(bool isAnswered, string reason);
private sealed record AgentSelectionResult(string name, string reason);
private sealed class AgentTerminationStrategy : TerminationStrategy
{
/// <inheritdoc/>
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
return Task.FromResult(true);
}
}
}
@@ -0,0 +1,86 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
namespace Agents;
/// <summary>
/// Sample showing how declarative agents can be defined through JSON manifest files.
/// Demonstrates how to load and configure an agent from a declarative manifest that specifies:
/// - The agent's identity (name, description, instructions)
/// - The agent's available actions/plugins
/// - Authentication parameters for accessing external services
/// </summary>
/// <remarks>
/// The test uses a SchedulingAssistant example that can:
/// - Read emails for meeting requests
/// - Check calendar availability
/// - Process scheduling-related tasks
/// The agent is configured via "SchedulingAssistant.json" manifest which defines the required
/// plugins and capabilities.
/// </remarks>
public class DeclarativeAgents(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task LoadsAgentFromDeclarativeAgentManifest(bool useChatClient)
{
var agentFileName = "SchedulingAssistant.json";
var input = "Read the body of my last five emails, if any contain a meeting request for today, check that it's already on my calendar, if not, call out which email it is.";
var kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient);
kernel.AutoFunctionInvocationFilters.Add(new ExpectedSchemaFunctionFilter());
var manifestLookupDirectory = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "Resources", "DeclarativeAgents");
var manifestFilePath = Path.Combine(manifestLookupDirectory, agentFileName);
var parameters = await CopilotAgentBasedPlugins.GetAuthenticationParametersAsync();
var agent = await kernel.CreateChatCompletionAgentFromDeclarativeAgentManifestAsync<ChatCompletionAgent>(manifestFilePath, parameters);
Assert.NotNull(agent);
Assert.NotNull(agent.Name);
Assert.NotEmpty(agent.Name);
Assert.NotNull(agent.Description);
Assert.NotEmpty(agent.Description);
Assert.NotNull(agent.Instructions);
Assert.NotEmpty(agent.Instructions);
ChatHistoryAgentThread agentThread = new();
var kernelArguments = new KernelArguments(new PromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(
options: new FunctionChoiceBehaviorOptions
{
AllowStrictSchemaAdherence = true
}
)
});
var responses = await agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, input), agentThread, options: new() { KernelArguments = kernelArguments }).ToArrayAsync();
Assert.NotEmpty(responses);
chatClient?.Dispose();
}
private sealed class ExpectedSchemaFunctionFilter : IAutoFunctionInvocationFilter
{
//TODO: this eventually needs to be added to all CAP or DA but we're still discussing where should those facilitators live
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
await next(context);
if (context.Result.ValueType == typeof(RestApiOperationResponse))
{
var openApiResponse = context.Result.GetValue<RestApiOperationResponse>();
if (openApiResponse?.ExpectedSchema is not null)
{
openApiResponse.ExpectedSchema = null;
}
}
}
}
}
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate that two different agent types are able to participate in the same conversation.
/// In this case a <see cref="ChatCompletionAgent"/> and <see cref="OpenAIAssistantAgent"/> participate.
/// </summary>
public class MixedChat_Agents(ITestOutputHelper output) : BaseAssistantTest(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 is 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 ChatWithOpenAIAssistantAgentAndChatCompletionAgent(bool useChatClient)
{
// Define the agents: one of each type
ChatCompletionAgent agentReviewer =
new()
{
Instructions = ReviewerInstructions,
Name = ReviewerName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: CopyWriterName,
instructions: CopyWriterInstructions,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agentWriter = new(assistant, this.AssistantClient);
// 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}]");
chatClient?.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,85 @@
// 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 Agents;
/// <summary>
/// Demonstrate <see cref="ChatCompletionAgent"/> agent interacts with
/// <see cref="OpenAIAssistantAgent"/> when it produces file output.
/// </summary>
public class MixedChat_Files(ITestOutputHelper output) : BaseAssistantTest(output)
{
private const string SummaryInstructions = "Summarize the entire conversation for the user in natural language.";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task AnalyzeFileAndGenerateReport(bool useChatClient)
{
await using Stream stream = EmbeddedResource.ReadStream("30-user-context.txt")!;
string fileId = await this.Client.UploadAssistantFileAsync(stream, "30-user-context.txt");
// Define the agents
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
enableCodeInterpreter: true,
codeInterpreterFileIds: [fileId],
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent analystAgent = new(assistant, this.AssistantClient);
ChatCompletionAgent summaryAgent =
new()
{
Instructions = SummaryInstructions,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Create a chat for agent interaction.
AgentGroupChat chat = new();
// Respond to user input
try
{
await InvokeAgentAsync(
analystAgent,
"""
Create a tab delimited file report of the ordered (descending) frequency distribution
of words in the file '30-user-context.txt' for any words used more than once.
""");
await InvokeAgentAsync(summaryAgent);
}
finally
{
await this.AssistantClient.DeleteAssistantAsync(analystAgent.Id);
await this.Client.DeleteFileAsync(fileId);
}
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(Agent agent, string? input = null)
{
if (!string.IsNullOrWhiteSpace(input))
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(new(AuthorRole.User, input));
this.WriteAgentChatMessage(message);
}
await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
this.WriteAgentChatMessage(response);
await this.DownloadResponseContentAsync(response);
}
}
}
}
@@ -0,0 +1,97 @@
// 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 Agents;
/// <summary>
/// Demonstrate <see cref="ChatCompletionAgent"/> agent interacts with
/// <see cref="OpenAIAssistantAgent"/> when it produces image output.
/// </summary>
public class MixedChat_Images(ITestOutputHelper output) : BaseAssistantTest(output)
{
private const string AnalystName = "Analyst";
private const string AnalystInstructions = "Create charts as requested without explanation.";
private const string SummarizerName = "Summarizer";
private const string SummarizerInstructions = "Summarize the entire conversation for the user in natural language.";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task AnalyzeDataAndGenerateChartAsync(bool useChatClient)
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: AnalystName,
instructions: AnalystInstructions,
enableCodeInterpreter: true,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent analystAgent = new(assistant, this.AssistantClient);
ChatCompletionAgent summaryAgent =
new()
{
Instructions = SummarizerInstructions,
Name = SummarizerName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Create a chat for agent interaction.
AgentGroupChat chat = new();
// Respond to user input
try
{
await InvokeAgentAsync(
analystAgent,
"""
Graph the percentage of storm events by state using a pie chart:
State, StormCount
TEXAS, 4701
KANSAS, 3166
IOWA, 2337
ILLINOIS, 2022
MISSOURI, 2016
GEORGIA, 1983
MINNESOTA, 1881
WISCONSIN, 1850
NEBRASKA, 1766
NEW YORK, 1750
""");
await InvokeAgentAsync(summaryAgent);
}
finally
{
await this.AssistantClient.DeleteAssistantAsync(analystAgent.Id);
}
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(Agent agent, string? input = null)
{
if (!string.IsNullOrWhiteSpace(input))
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
}
await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
this.WriteAgentChatMessage(response);
await this.DownloadResponseImageAsync(response);
}
}
}
}
@@ -0,0 +1,88 @@
// 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 Agents;
/// <summary>
/// Demonstrate the use of <see cref="AgentChat.ResetAsync"/>.
/// </summary>
public class MixedChat_Reset(ITestOutputHelper output) : BaseAssistantTest(output)
{
private const string AgentInstructions =
"""
The user may either provide information or query on information previously provided.
If the query does not correspond with information provided, inform the user that their query cannot be answered.
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ResetChat(bool useChatClient)
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
instructions: AgentInstructions,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent assistantAgent = new(assistant, this.AssistantClient);
ChatCompletionAgent chatAgent =
new()
{
Name = nameof(ChatCompletionAgent),
Instructions = AgentInstructions,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Create a chat for agent interaction.
AgentGroupChat chat = new();
// Respond to user input
try
{
await InvokeAgentAsync(assistantAgent, "What is my favorite color?");
await InvokeAgentAsync(chatAgent);
await InvokeAgentAsync(assistantAgent, "I like green.");
await InvokeAgentAsync(chatAgent);
await InvokeAgentAsync(assistantAgent, "What is my favorite color?");
await InvokeAgentAsync(chatAgent);
await chat.ResetAsync();
await InvokeAgentAsync(assistantAgent, "What is my favorite color?");
await InvokeAgentAsync(chatAgent);
}
finally
{
await chat.ResetAsync();
await this.AssistantClient.DeleteAssistantAsync(assistantAgent.Id);
}
chatClient?.Dispose();
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(Agent agent, string? input = null)
{
if (!string.IsNullOrWhiteSpace(input))
{
ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(message);
this.WriteAgentChatMessage(message);
}
await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
this.WriteAgentChatMessage(response);
}
}
}
}
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate the serialization of <see cref="AgentGroupChat"/> with a <see cref="ChatCompletionAgent"/>
/// and an <see cref="OpenAIAssistantAgent"/>.
/// </summary>
public class MixedChat_Serialization(ITestOutputHelper output) : BaseAssistantTest(output)
{
private const string TranslatorName = "Translator";
private const string TranslatorInstructions =
"""
Spell the last number in chat as a word in english and spanish on a single line without any line breaks.
""";
private const string CounterName = "Counter";
private const string CounterInstructions =
"""
Increment the last number from your most recent response.
Never repeat the same number.
Only respond with a single number that is the result of your calculation without explanation.
""";
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task SerializeAndRestoreAgentGroupChat(bool useChatClient)
{
// Define the agents: one of each type
ChatCompletionAgent agentTranslator =
new()
{
Instructions = TranslatorInstructions,
Name = TranslatorName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: CounterName,
instructions: CounterInstructions,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agentCounter = new(assistant, this.AssistantClient);
AgentGroupChat chat = CreateGroupChat();
// Invoke chat and display messages.
ChatMessageContent input = new(AuthorRole.User, "1");
chat.AddChatMessage(input);
this.WriteAgentChatMessage(input);
Console.WriteLine("============= Dynamic Agent Chat - Primary (prior to serialization) ==============");
await InvokeAgents(chat);
AgentGroupChat copy = CreateGroupChat();
Console.WriteLine("\n=========== Serialize and restore the Agent Chat into a new instance ============");
await CloneChatAsync(chat, copy);
Console.WriteLine("\n============ Continue with the dynamic Agent Chat (after deserialization) ===============");
await InvokeAgents(copy);
Console.WriteLine("\n============ The entire Agent Chat (includes messages prior to serialization and those after deserialization) ==============");
await foreach (ChatMessageContent content in copy.GetChatMessagesAsync())
{
this.WriteAgentChatMessage(content);
}
chatClient?.Dispose();
async Task InvokeAgents(AgentGroupChat chat)
{
await foreach (ChatMessageContent content in chat.InvokeAsync())
{
this.WriteAgentChatMessage(content);
}
}
async Task CloneChatAsync(AgentGroupChat source, AgentGroupChat clone)
{
await using MemoryStream stream = new();
await AgentChatSerializer.SerializeAsync(source, stream);
stream.Position = 0;
using StreamReader reader = new(stream);
Console.WriteLine(await reader.ReadToEndAsync());
stream.Position = 0;
AgentChatSerializer serializer = await AgentChatSerializer.DeserializeAsync(stream);
await serializer.DeserializeAsync(clone);
}
AgentGroupChat CreateGroupChat() =>
new(agentTranslator, agentCounter)
{
ExecutionSettings =
new()
{
TerminationStrategy =
new CountingTerminationStrategy(5)
{
// Only the art-director may approve.
Agents = [agentTranslator],
// Limit total number of turns
MaximumIterations = 20,
}
}
};
}
private sealed class CountingTerminationStrategy(int maxTurns) : TerminationStrategy
{
private int _count = 0;
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken)
{
++this._count;
bool shouldTerminate = this._count >= maxTurns;
return Task.FromResult(shouldTerminate);
}
}
}
@@ -0,0 +1,127 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate consuming "streaming" message for <see cref="ChatCompletionAgent"/> and
/// <see cref="OpenAIAssistantAgent"/> both participating in an <see cref="AgentChat"/>.
/// </summary>
public class MixedChat_Streaming(ITestOutputHelper output) : BaseAssistantTest(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 is 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 UseStreamingAgentChat(bool useChatClient)
{
// Define the agents: one of each type
ChatCompletionAgent agentReviewer =
new()
{
Instructions = ReviewerInstructions,
Name = ReviewerName,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out var chatClient),
};
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: CopyWriterName,
instructions: CopyWriterInstructions,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agentWriter = new(assistant, this.AssistantClient);
// 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);
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]);
}
Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]");
chatClient?.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,74 @@
// 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 Agents;
/// <summary>
/// Demonstrate using code-interpreter with <see cref="OpenAIAssistantAgent"/> to
/// produce image content displays the requested charts.
/// </summary>
public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task GenerateChartWithOpenAIAssistantAgentAsync()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
"ChartMaker",
instructions: "Create charts as requested without explanation.",
enableCodeInterpreter: true,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
AgentThread? agentThread = null;
// Respond to user input
try
{
await InvokeAgentAsync(
"""
Display this data using a bar-chart (not stacked):
Banding Brown Pink Yellow Sum
X00000 339 433 126 898
X00300 48 421 222 691
X12345 16 395 352 763
Others 23 373 156 552
Sum 426 1622 856 2904
""");
await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?");
}
finally
{
if (agentThread is not null)
{
await agentThread.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 (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(message))
{
this.WriteAgentChatMessage(response);
await this.DownloadResponseImageAsync(response);
agentThread = response.Thread;
}
}
}
}
@@ -0,0 +1,67 @@
// 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 Agents;
/// <summary>
/// Demonstrate using code-interpreter to manipulate and generate csv files with <see cref="OpenAIAssistantAgent"/> .
/// </summary>
public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync()
{
await using Stream stream = EmbeddedResource.ReadStream("sales.csv")!;
string fileId = await this.Client.UploadAssistantFileAsync(stream, "sales.csv");
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
enableCodeInterpreter: true,
codeInterpreterFileIds: [fileId],
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
AgentThread? agentThread = null;
// Respond to user input
try
{
await InvokeAgentAsync("Which segment had the most sales?");
await InvokeAgentAsync("List the top 5 countries that generated the most profit.");
await InvokeAgentAsync("Create a tab delimited file report of profit by each country per month.");
}
finally
{
if (agentThread is not null)
{
await agentThread.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(string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(message))
{
this.WriteAgentChatMessage(response);
await this.DownloadResponseContentAsync(response);
agentThread = response.Thread;
}
}
}
}
@@ -0,0 +1,211 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate usage of <see cref="IAutoFunctionInvocationFilter"/> for and
/// <see cref="IFunctionInvocationFilter"/> filters with <see cref="OpenAIAssistantAgent"/>
/// via <see cref="AgentChat"/>.
/// </summary>
public class OpenAIAssistant_FunctionFilters(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseFunctionInvocationFilterAsync()
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAsync(CreateKernelWithInvokeFilter());
// Invoke assistant agent (non streaming)
await InvokeAssistantAsync(agent);
}
[Fact]
public async Task UseFunctionInvocationFilterStreamingAsync()
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAsync(CreateKernelWithInvokeFilter());
// Invoke assistant agent (streaming)
await InvokeAssistantStreamingAsync(agent);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task UseAutoFunctionInvocationFilterAsync(bool terminate)
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAsync(CreateKernelWithAutoFilter(terminate));
// Invoke assistant agent (non streaming)
await InvokeAssistantAsync(agent);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task UseAutoFunctionInvocationFilterWithStreamingAgentInvocationAsync(bool terminate)
{
// Define the agent
OpenAIAssistantAgent agent = await CreateAssistantAsync(CreateKernelWithAutoFilter(terminate));
// Invoke assistant agent (streaming)
await InvokeAssistantStreamingAsync(agent);
}
private async Task InvokeAssistantAsync(OpenAIAssistantAgent agent)
{
OpenAIAssistantAgentThread agentThread = new(this.AssistantClient);
try
{
// Respond to user input, invoking functions where appropriate.
ChatMessageContent message = new(AuthorRole.User, "What is the special soup?");
await agent.InvokeAsync(message, agentThread).ToArrayAsync();
// Display the entire chat history.
ChatMessageContent[] history = await agentThread.GetMessagesAsync(MessageCollectionOrder.Ascending).ToArrayAsync();
this.WriteChatHistory(history);
}
finally
{
await agentThread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
}
private async Task InvokeAssistantStreamingAsync(OpenAIAssistantAgent agent)
{
OpenAIAssistantAgentThread agentThread = new(this.AssistantClient);
try
{
// Respond to user input, invoking functions where appropriate.
ChatMessageContent message = new(AuthorRole.User, "What is the special soup?");
await agent.InvokeStreamingAsync(message, agentThread).ToArrayAsync();
// Display the entire chat history.
ChatMessageContent[] history = await agentThread.GetMessagesAsync(MessageCollectionOrder.Ascending).ToArrayAsync();
this.WriteChatHistory(history);
}
finally
{
await agentThread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
}
private void WriteChatHistory(IEnumerable<ChatMessageContent> history)
{
Console.WriteLine("\n================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
foreach (ChatMessageContent message in history)
{
this.WriteAgentChatMessage(message);
}
}
private async Task<OpenAIAssistantAgent> CreateAssistantAsync(Kernel kernel)
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
instructions: "Answer questions about the menu.",
metadata: SampleMetadata);
// Create the agent
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient, [plugin])
{
Kernel = kernel
};
return agent;
}
private Kernel CreateKernelWithAutoFilter(bool terminate)
{
IKernelBuilder builder = Kernel.CreateBuilder();
base.AddChatCompletionToKernel(builder);
builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new AutoInvocationFilter(terminate));
return builder.Build();
}
private Kernel CreateKernelWithInvokeFilter()
{
IKernelBuilder builder = Kernel.CreateBuilder();
base.AddChatCompletionToKernel(builder);
builder.Services.AddSingleton<IFunctionInvocationFilter>(new InvocationFilter());
return builder.Build();
}
private sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials()
{
return
"""
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
""";
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice([Description("The name of the menu item.")] string menuItem)
{
return "$9.99";
}
}
private sealed class InvocationFilter() : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"FILTER INVOKED {nameof(InvocationFilter)} - {context.Function.Name}");
// Execution the function
await next(context);
// Signal termination if the function is from the MenuPlugin
if (context.Function.PluginName == nameof(MenuPlugin))
{
context.Result = new FunctionResult(context.Function, "BLOCKED");
}
}
}
private sealed class AutoInvocationFilter(bool terminate = true) : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"FILTER INVOKED {nameof(AutoInvocationFilter)} - {context.Function.Name}");
// Execution the function
await next(context);
// Signal termination if the function is from the MenuPlugin
if (context.Function.PluginName == nameof(MenuPlugin))
{
context.Terminate = terminate;
}
}
}
}
@@ -0,0 +1,181 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate consuming "streaming" message for <see cref="OpenAIAssistantAgent"/>.
/// </summary>
public class OpenAIAssistant_Streaming(ITestOutputHelper output) : BaseAssistantTest(output)
{
[Fact]
public async Task UseStreamingAssistantAgentAsync()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: "Parrot",
instructions: "Repeat the user message in the voice of a pirate and then end with a parrot sound.",
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
// Create a thread for the agent conversation.
OpenAIAssistantAgentThread agentThread = new(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Fortune favors the bold.");
await InvokeAgentAsync(agent, agentThread, "I came, I saw, I conquered.");
await InvokeAgentAsync(agent, agentThread, "Practice makes perfect.");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
[Fact]
public async Task UseStreamingAssistantAgentWithPluginAsync()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: "Host",
instructions: "Answer questions about the menu.",
metadata: SampleMetadata);
// Create the agent
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient, [plugin]);
// Create a thread for the agent conversation.
OpenAIAssistantAgentThread agentThread = new(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, agentThread, "What is the special drink and its price?");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
[Fact]
public async Task UseStreamingAssistantWithCodeInterpreterAsync()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
name: "MathGuy",
instructions: "Solve math problems with code.",
enableCodeInterpreter: true,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient);
// Create a thread for the agent conversation.
OpenAIAssistantAgentThread agentThread = new(this.AssistantClient, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Is 191 a prime number?");
await InvokeAgentAsync(agent, agentThread, "Determine the values in the Fibonacci sequence that that are less then the value of 101");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, AgentThread agentThread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);
// For this sample, also capture fully formed messages so we can display them later.
ChatHistory history = [];
Task OnNewMessage(ChatMessageContent message)
{
history.Add(message);
return Task.CompletedTask;
}
bool isFirst = false;
bool isCode = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread, new() { OnIntermediateMessage = OnNewMessage }))
{
if (string.IsNullOrEmpty(response.Content))
{
StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType<StreamingFunctionCallUpdateContent>().SingleOrDefault();
if (functionCall?.Name != null)
{
(string? pluginName, string functionName) = this.ParseFunctionName(functionCall.Name);
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {$"{pluginName}." ?? string.Empty}{functionName}");
}
continue;
}
// Differentiate between assistant and tool messages
if (isCode != (response.Metadata?.ContainsKey(OpenAIAssistantAgent.CodeInterpreterMetadataKey) ?? false))
{
isFirst = false;
isCode = !isCode;
}
if (!isFirst)
{
Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
isFirst = true;
}
Console.WriteLine($"\t > streamed: '{response.Content}'");
}
foreach (ChatMessageContent content in history)
{
this.WriteAgentChatMessage(content);
}
}
private async Task DisplayChatHistoryAsync(OpenAIAssistantAgentThread agentThread)
{
Console.WriteLine("================================");
Console.WriteLine("CHAT HISTORY");
Console.WriteLine("================================");
ChatMessageContent[] messages = await agentThread.GetMessagesAsync().ToArrayAsync();
for (int index = messages.Length - 1; index >= 0; --index)
{
this.WriteAgentChatMessage(messages[index]);
}
}
public sealed class MenuPlugin
{
[KernelFunction, Description("Provides a list of specials from the menu.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string GetSpecials()
{
return
"""
Special Soup: Clam Chowder
Special Salad: Cobb Salad
Special Drink: Chai Tea
""";
}
[KernelFunction, Description("Provides the price of the requested menu item.")]
public string GetItemPrice(
[Description("The name of the menu item.")]
string menuItem)
{
return "$9.99";
}
}
}
@@ -0,0 +1,149 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;
using OpenAI.Assistants;
namespace Agents;
/// <summary>
/// Demonstrate parameterized template instruction for <see cref="OpenAIAssistantAgent"/>.
/// </summary>
public class OpenAIAssistant_Templating(ITestOutputHelper output) : BaseAssistantTest(output)
{
private static readonly (string Input, string? Style)[] s_inputs =
[
(Input: "Home cooking is great.", Style: null),
(Input: "Talk about world peace.", Style: "iambic pentameter"),
(Input: "Say something about doing your best.", Style: "e. e. cummings"),
(Input: "What do you think about having fun?", Style: "old school rap")
];
[Fact]
public async Task InvokeAgentWithInstructionsAsync()
{
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantAsync(
this.Model,
instructions:
"""
Write a one verse poem on the requested topic in the styles of {{$style}}.
Always state the requested style of the poem.
""",
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient)
{
Arguments = new()
{
{"style", "haiku"}
},
};
await InvokeAssistantAgentWithTemplateAsync(agent);
}
[Fact]
public async Task InvokeAgentWithKernelTemplateAsync()
{
// Default factory is KernelPromptTemplateFactory
await InvokeAssistantAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the styles of {{$style}}.
Always state the requested style of the poem.
""",
PromptTemplateConfig.SemanticKernelTemplateFormat,
new KernelPromptTemplateFactory());
}
[Fact]
public async Task InvokeAgentWithHandlebarsTemplateAsync()
{
await InvokeAssistantAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the styles of {{style}}.
Always state the requested style of the poem.
""",
HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat,
new HandlebarsPromptTemplateFactory());
}
[Fact]
public async Task InvokeAgentWithLiquidTemplateAsync()
{
await InvokeAssistantAgentWithTemplateAsync(
"""
Write a one verse poem on the requested topic in the styles of {{style}}.
Always state the requested style of the poem.
""",
LiquidPromptTemplateFactory.LiquidTemplateFormat,
new LiquidPromptTemplateFactory());
}
private async Task InvokeAssistantAgentWithTemplateAsync(
string instructionTemplate,
string templateFormat,
IPromptTemplateFactory templateFactory)
{
PromptTemplateConfig config = new()
{
Template = instructionTemplate,
TemplateFormat = templateFormat,
};
// Define the assistant
Assistant assistant =
await this.AssistantClient.CreateAssistantFromTemplateAsync(
this.Model,
config,
metadata: SampleMetadata);
// Create the agent
OpenAIAssistantAgent agent = new(assistant, this.AssistantClient, plugins: null, templateFactory, templateFormat)
{
Arguments = new()
{
{"style", "haiku"}
},
};
await InvokeAssistantAgentWithTemplateAsync(agent);
}
private async Task InvokeAssistantAgentWithTemplateAsync(OpenAIAssistantAgent agent)
{
// Create a thread for the agent conversation.
OpenAIAssistantAgentThread thread = new(this.AssistantClient, metadata: SampleMetadata);
try
{
// Respond to user input
foreach ((string input, string? style) in s_inputs)
{
ChatMessageContent request = new(AuthorRole.User, input);
this.WriteAgentChatMessage(request);
KernelArguments? arguments = null;
if (!string.IsNullOrWhiteSpace(style))
{
arguments = new() { { "style", style } };
}
await foreach (ChatMessageContent message in agent.InvokeAsync(request, thread, options: new() { KernelArguments = arguments }))
{
this.WriteAgentChatMessage(message);
}
}
}
finally
{
await thread.DeleteAsync();
await this.AssistantClient.DeleteAssistantAsync(agent.Id);
}
}
}
@@ -0,0 +1,135 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Memory;
namespace Agents;
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
/// <summary>
/// Demonstrate creation of <see cref="OpenAIResponseAgent"/> and
/// adding whiteboarding capabilities, where the most relevant information from the conversation is captured on a whiteboard.
/// This is useful for long running conversations where the conversation history may need to be truncated
/// over time, but you do not want to agent to lose context.
/// </summary>
public class OpenAIResponseAgent_Whiteboard(ITestOutputHelper output) : BaseResponsesAgentTest(output)
{
private const string AgentName = "FriendlyAssistant";
private const string AgentInstructions = "You are a friendly assistant";
/// <summary>
/// Shows how to allow an agent to use a whiteboard for storing the most important information
/// from a long running, truncated conversation.
/// </summary>
[Fact]
private async Task UseWhiteboardForShortTermMemory()
{
IChatClient chatClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new AzureCliCredential())
.GetChatClient(TestConfiguration.AzureOpenAI.ChatDeploymentName)
.AsIChatClient();
// Create the whiteboard.
WhiteboardProvider whiteboardProvider = new(chatClient);
OpenAIResponseAgent agent = new(this.Client)
{
Name = AgentName,
Instructions = AgentInstructions,
Arguments = new KernelArguments(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
StoreEnabled = false,
};
// Create the agent with our sample plugin.
agent.Kernel.Plugins.AddFromType<VMPlugin>();
// Create a chat history reducer that we can use to truncate the chat history
// when it goes over 3 items.
ChatHistoryTruncationReducer chatHistoryReducer = new(3, 3);
// Create a thread for the agent and add the whiteboard to it.
ChatHistoryAgentThread agentThread = new();
agentThread.AIContextProviders.Add(whiteboardProvider);
// Simulate a conversation with the agent.
// We will also truncate the conversation once it goes over a few items.
await InvokeWithConsoleWriteLine("Hello");
await InvokeWithConsoleWriteLine("I'd like to create a VM?");
await InvokeWithConsoleWriteLine("I want it to have 3 cores.");
await InvokeWithConsoleWriteLine("I want it to have 48GB of RAM.");
await InvokeWithConsoleWriteLine("I want it to have a 500GB Harddrive.");
await InvokeWithConsoleWriteLine("I want it in Europe.");
await InvokeWithConsoleWriteLine("Can you make it Linux and call it 'ContosoVM'.");
await InvokeWithConsoleWriteLine("OK, let's call it `ContosoFinanceVM_Europe` instead.");
await InvokeWithConsoleWriteLine("Thanks, now I want to create another VM.");
await InvokeWithConsoleWriteLine("Make all the options the same as the last one, except for the region, which should be North America, and the name, which should be 'ContosoFinanceVM_NorthAmerica'.");
async Task InvokeWithConsoleWriteLine(string message)
{
// Print the user input.
Console.WriteLine($"User: {message}");
// Invoke the agent.
ChatMessageContent response = await agent.InvokeAsync(message, agentThread).FirstAsync();
// Print the response.
this.WriteAgentChatMessage(response);
// Make sure any async whiteboard processing is complete before we print out its contents.
await whiteboardProvider.WhenProcessingCompleteAsync();
// Print out the whiteboard contents.
Console.WriteLine("Whiteboard contents:");
foreach (var item in whiteboardProvider.CurrentWhiteboardContent)
{
Console.WriteLine($"- {item}");
}
Console.WriteLine();
// Truncate the chat history if it gets too big.
await agentThread.ChatHistory.ReduceInPlaceAsync(chatHistoryReducer, CancellationToken.None);
}
}
private sealed class VMPlugin
{
[KernelFunction]
public Task<VMCreateResult> CreateVM(Region region, OperatingSystem os, string name, int numberOfCores, int memorySizeInGB, int hddSizeInGB)
{
if (name == "ContosoVM")
{
throw new Exception("VM name already exists");
}
return Task.FromResult(new VMCreateResult { VMId = Guid.NewGuid().ToString() });
}
}
public class VMCreateResult
{
public string VMId { get; set; } = string.Empty;
}
private enum Region
{
NorthAmerica,
SouthAmerica,
Europe,
Asia,
Africa,
Australia
}
private enum OperatingSystem
{
Windows,
Linux,
MacOS
}
}
+89
View File
@@ -0,0 +1,89 @@
# Semantic Kernel: Agent syntax examples
This project contains a collection of examples on how to use _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 concept agents examples are grouped by prefix:
Prefix|Description
---|---
OpenAIAssistant|How to use agents based on the [Open AI Assistant API](https://platform.openai.com/docs/assistants).
MixedChat|How to combine different agent types.
ComplexChat|How to deveop complex agent chat solutions.
Legacy|How to use the legacy _Experimental Agent API_.
## 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
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 OpenAIAssistant_CodeInterpreter
```
## 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 Open AI:
```
dotnet user-secrets set "AzureOpenAI:DeploymentName" "..."
dotnet user-secrets set "AzureOpenAI:ChatDeploymentName" "..."
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://... .openai.azure.com/"
dotnet user-secrets set "AzureOpenAI:ApiKey" "..."
```
> NOTE: Azure secrets will take precedence, if both Open AI and Azure Open AI secrets are defined, unless `ForceOpenAI` is set:
```
protected override bool ForceOpenAI => true;
```
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AudioToText;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Resources;
namespace AudioToText;
/// <summary>
/// Represents a class that demonstrates audio processing functionality.
/// </summary>
public sealed class OpenAI_AudioToText(ITestOutputHelper output) : BaseTest(output)
{
private const string AudioToTextModel = "whisper-1";
private const string AudioFilename = "test_audio.wav";
[Fact(Skip = "Setup and run TextToAudioAsync before running this test.")]
public async Task AudioToTextAsync()
{
// Create a kernel with OpenAI audio to text service
var kernel = Kernel.CreateBuilder()
.AddOpenAIAudioToText(
modelId: AudioToTextModel,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
var audioToTextService = kernel.GetRequiredService<IAudioToTextService>();
// Set execution settings (optional)
OpenAIAudioToTextExecutionSettings executionSettings = new(AudioFilename)
{
Language = "en", // The language of the audio data as two-letter ISO-639-1 language code (e.g. 'en' or 'es').
Prompt = "sample prompt", // An optional text to guide the model's style or continue a previous audio segment.
// The prompt should match the audio language.
ResponseFormat = "json", // The format to return the transcribed text in.
// Supported formats are json, text, srt, verbose_json, or vtt. Default is 'json'.
Temperature = 0.3f, // The randomness of the generated text.
// Select a value from 0.0 to 1.0. 0 is the default.
};
// Read audio content from a file
await using var audioFileStream = EmbeddedResource.ReadStream(AudioFilename);
var audioFileBinaryData = await BinaryData.FromStreamAsync(audioFileStream!);
AudioContent audioContent = new(audioFileBinaryData, mimeType: null);
// Convert audio to text
var textContent = await audioToTextService.GetTextContentAsync(audioContent, executionSettings);
// Output the transcribed text
Console.WriteLine(textContent.Text);
}
}
@@ -0,0 +1,301 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
namespace Caching;
/// <summary>
/// This example shows how to achieve Semantic Caching with Filters.
/// <see cref="IPromptRenderFilter"/> is used to get rendered prompt and check in cache if similar prompt was already answered.
/// If there is a record in cache, then previously cached answer will be returned to the user instead of making a call to LLM.
/// If there is no record in cache, a call to LLM will be performed, and result will be cached together with rendered prompt.
/// <see cref="IFunctionInvocationFilter"/> is used to update cache with rendered prompt and related LLM result.
/// </summary>
public class SemanticCachingWithFilters(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Executing similar requests two times using in-memory caching store to compare execution time and results.
/// Second execution is faster, because the result is returned from cache.
/// </summary>
[Fact]
public async Task InMemoryCacheAsync()
{
var kernel = GetKernelWithCache(services =>
{
services.AddInMemoryVectorStore();
});
var result1 = await ExecuteAsync(kernel, "First run", "What's the tallest building in New York?");
var result2 = await ExecuteAsync(kernel, "Second run", "What is the highest building in New York City?");
Console.WriteLine($"Result 1: {result1}");
Console.WriteLine($"Result 2: {result2}");
/*
Output:
First run: What's the tallest building in New York?
Elapsed Time: 00:00:03.828
Second run: What is the highest building in New York City?
Elapsed Time: 00:00:00.541
Result 1: The tallest building in New York is One World Trade Center, also known as Freedom Tower.It stands at 1,776 feet(541.3 meters) tall, including its spire.
Result 2: The tallest building in New York is One World Trade Center, also known as Freedom Tower.It stands at 1,776 feet(541.3 meters) tall, including its spire.
*/
}
/// <summary>
/// Executing similar requests two times using Redis caching store to compare execution time and results.
/// Second execution is faster, because the result is returned from cache.
/// How to run Redis on Docker locally: https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/docker/.
/// </summary>
[Fact]
public async Task RedisCacheAsync()
{
var kernel = GetKernelWithCache(services =>
{
services.AddRedisVectorStore("localhost:6379");
});
var result1 = await ExecuteAsync(kernel, "First run", "What's the tallest building in New York?");
var result2 = await ExecuteAsync(kernel, "Second run", "What is the highest building in New York City?");
Console.WriteLine($"Result 1: {result1}");
Console.WriteLine($"Result 2: {result2}");
/*
First run: What's the tallest building in New York?
Elapsed Time: 00:00:03.674
Second run: What is the highest building in New York City?
Elapsed Time: 00:00:00.292
Result 1: The tallest building in New York is One World Trade Center, also known as Freedom Tower. It stands at 1,776 feet (541 meters) tall, including its spire.
Result 2: The tallest building in New York is One World Trade Center, also known as Freedom Tower. It stands at 1,776 feet (541 meters) tall, including its spire.
*/
}
/// <summary>
/// Executing similar requests two times using Azure Cosmos DB for MongoDB caching store to compare execution time and results.
/// Second execution is faster, because the result is returned from cache.
/// How to setup Azure Cosmos DB for MongoDB cluster: https://learn.microsoft.com/en-gb/azure/cosmos-db/mongodb/vcore/quickstart-portal
/// </summary>
[Fact]
public async Task CosmosMongoDBCacheAsync()
{
var kernel = GetKernelWithCache(services =>
{
services.AddCosmosMongoVectorStore(
TestConfiguration.CosmosMongo.ConnectionString,
TestConfiguration.CosmosMongo.DatabaseName);
});
var result1 = await ExecuteAsync(kernel, "First run", "What's the tallest building in New York?");
var result2 = await ExecuteAsync(kernel, "Second run", "What is the highest building in New York City?");
Console.WriteLine($"Result 1: {result1}");
Console.WriteLine($"Result 2: {result2}");
/*
First run: What's the tallest building in New York?
Elapsed Time: 00:00:05.485
Second run: What is the highest building in New York City?
Elapsed Time: 00:00:00.389
Result 1: The tallest building in New York is One World Trade Center, also known as Freedom Tower, which stands at 1,776 feet (541.3 meters) tall.
Result 2: The tallest building in New York is One World Trade Center, also known as Freedom Tower, which stands at 1,776 feet (541.3 meters) tall.
*/
}
#region Configuration
/// <summary>
/// Returns <see cref="Kernel"/> instance with required registered services.
/// </summary>
private Kernel GetKernelWithCache(Action<IServiceCollection> configureVectorStore)
{
var builder = Kernel.CreateBuilder();
if (!string.IsNullOrWhiteSpace(TestConfiguration.AzureOpenAI.ApiKey))
{
// Add Azure OpenAI chat completion service
builder.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
// Add Azure OpenAI embedding generator
builder.AddAzureOpenAIEmbeddingGenerator(
TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
}
else
{
// Add Azure OpenAI chat completion service
builder.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
new AzureCliCredential());
// Add Azure OpenAI embedding generator
builder.AddAzureOpenAIEmbeddingGenerator(
TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
new AzureCliCredential());
}
// Add vector store for caching purposes (e.g. in-memory, Redis, Azure Cosmos DB)
configureVectorStore(builder.Services);
// Add prompt render filter to query cache and check if rendered prompt was already answered.
builder.Services.AddSingleton<IPromptRenderFilter, PromptCacheFilter>();
// Add function invocation filter to cache rendered prompts and LLM results.
builder.Services.AddSingleton<IFunctionInvocationFilter, FunctionCacheFilter>();
return builder.Build();
}
#endregion
#region Cache Filters
/// <summary>
/// Base class for filters that contains common constant values.
/// </summary>
public class CacheBaseFilter
{
/// <summary>
/// Collection/table name in cache to use.
/// </summary>
protected const string CollectionName = "llm_responses";
/// <summary>
/// Metadata key in function result for cache record id, which is used to overwrite previously cached response.
/// </summary>
protected const string RecordIdKey = "CacheRecordId";
}
/// <summary>
/// Filter which is executed during prompt rendering operation.
/// </summary>
public sealed class PromptCacheFilter(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
VectorStore vectorStore)
: CacheBaseFilter, IPromptRenderFilter
{
public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
{
// Trigger prompt rendering operation
await next(context);
// Get rendered prompt
var prompt = context.RenderedPrompt!;
var promptEmbedding = await embeddingGenerator.GenerateAsync(prompt);
var collection = vectorStore.GetCollection<string, CacheRecord>(CollectionName);
await collection.EnsureCollectionExistsAsync();
// Search for similar prompts in cache.
var searchResult = (await collection.SearchAsync(promptEmbedding, top: 1, cancellationToken: context.CancellationToken)
.FirstOrDefaultAsync())?.Record;
// If result exists, return it.
if (searchResult is not null)
{
// Override function result. This will prevent calling LLM and will return result immediately.
context.Result = new FunctionResult(context.Function, searchResult.Result)
{
Metadata = new Dictionary<string, object?> { [RecordIdKey] = searchResult.Id }
};
}
}
}
/// <summary>
/// Filter which is executed during function invocation.
/// </summary>
public sealed class FunctionCacheFilter(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
VectorStore vectorStore)
: CacheBaseFilter, IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func<Microsoft.SemanticKernel.FunctionInvocationContext, Task> next)
{
// Trigger function invocation
await next(context);
// Get function invocation result
var result = context.Result;
// If there was any rendered prompt, cache it together with LLM result for future calls.
if (!string.IsNullOrEmpty(context.Result.RenderedPrompt))
{
// Get cache record id if result was cached previously or generate new id.
var recordId = context.Result.Metadata?.GetValueOrDefault(RecordIdKey, Guid.NewGuid().ToString()) as string;
// Generate prompt embedding.
var promptEmbedding = await embeddingGenerator.GenerateAsync(context.Result.RenderedPrompt);
// Cache rendered prompt and LLM result.
var collection = vectorStore.GetCollection<string, CacheRecord>(CollectionName);
await collection.EnsureCollectionExistsAsync();
var cacheRecord = new CacheRecord
{
Id = recordId!,
Prompt = context.Result.RenderedPrompt,
Result = result.ToString(),
PromptEmbedding = promptEmbedding.Vector
};
await collection.UpsertAsync(cacheRecord, cancellationToken: context.CancellationToken);
}
}
}
#endregion
#region Execution
/// <summary>
/// Helper method to invoke prompt and measure execution time for comparison.
/// </summary>
private async Task<FunctionResult> ExecuteAsync(Kernel kernel, string title, string prompt)
{
Console.WriteLine($"{title}: {prompt}");
var stopwatch = Stopwatch.StartNew();
var result = await kernel.InvokePromptAsync(prompt);
stopwatch.Stop();
Console.WriteLine($@"Elapsed Time: {stopwatch.Elapsed:hh\:mm\:ss\.FFF}");
return result;
}
#endregion
#region Vector Store Record
private sealed class CacheRecord
{
[VectorStoreKey]
public string Id { get; set; }
[VectorStoreData]
public string Prompt { get; set; }
[VectorStoreData]
public string Result { get; set; }
[VectorStoreVector(Dimensions: 1536)]
public ReadOnlyMemory<float> PromptEmbedding { get; set; }
}
#endregion
}
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Azure.AI.Inference;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using chat completion with Azure Foundry or GitHub models.
/// Azure AI Foundry: https://ai.azure.com/explore/models
/// GitHub Models: https://github.com/marketplace?type=models
/// </summary>
public class AzureAIInference_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task ServicePromptAsync()
{
Console.WriteLine("======== Azure AI Inference - Chat Completion ========");
Assert.NotNull(TestConfiguration.AzureAIInference.ApiKey);
var chatService = new ChatCompletionsClient(
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey))
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
.AsChatCompletionService();
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);
// First assistant message
var reply = await chatService.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
OutputLastMessage(chatHistory);
// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
OutputLastMessage(chatHistory);
// Second assistant message
reply = await chatService.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
OutputLastMessage(chatHistory);
/* Output:
Chat content:
------------------------
System: You are a librarian, expert about books
------------------------
User: Hi, I'm looking for book suggestions
------------------------
Assistant: Sure, I'd be happy to help! What kind of books are you interested in? Fiction or non-fiction? Any particular genre?
------------------------
User: I love history and philosophy, I'd like to learn something new about Greece, any suggestion?
------------------------
Assistant: Great! For history and philosophy books about Greece, here are a few suggestions:
1. "The Greeks" by H.D.F. Kitto - This is a classic book that provides an overview of ancient Greek history and culture, including their philosophy, literature, and art.
2. "The Republic" by Plato - This is one of the most famous works of philosophy in the Western world, and it explores the nature of justice and the ideal society.
3. "The Peloponnesian War" by Thucydides - This is a detailed account of the war between Athens and Sparta in the 5th century BCE, and it provides insight into the political and military strategies of the time.
4. "The Iliad" by Homer - This epic poem tells the story of the Trojan War and is considered one of the greatest works of literature in the Western canon.
5. "The Histories" by Herodotus - This is a comprehensive account of the Persian Wars and provides a wealth of information about ancient Greek culture and society.
I hope these suggestions are helpful!
------------------------
*/
}
[Fact]
public async Task ChatPromptAsync()
{
StringBuilder chatPrompt = new("""
<message role="system">You are a librarian, expert about books</message>
<message role="user">Hi, I'm looking for book suggestions</message>
""");
var kernel = Kernel.CreateBuilder()
.AddAzureAIInferenceChatCompletion(
modelId: TestConfiguration.AzureAIInference.ChatModelId,
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
apiKey: TestConfiguration.AzureAIInference.ApiKey)
.Build();
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
Console.WriteLine(reply);
}
}
@@ -0,0 +1,154 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Azure.AI.Inference;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using streaming chat completion with Azure Foundry or GitHub models.
/// Azure AI Foundry: https://ai.azure.com/explore/models
/// GitHub Models: https://github.com/marketplace?type=models
/// </summary>
public class AzureAIInference_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// This example demonstrates chat completion streaming using OpenAI.
/// </summary>
[Fact]
public Task StreamChatAsync()
{
Console.WriteLine("======== Azure AI Inference - Chat Completion Streaming ========");
var chatService = new ChatCompletionsClient(
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey!))
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
.AsChatCompletionService();
return this.StartStreamingChatAsync(chatService);
}
/// <summary>
/// This example demonstrates chat completion streaming using OpenAI via the kernel.
/// </summary>
[Fact]
public async Task StreamChatPromptAsync()
{
Console.WriteLine("======== Azure AI Inference - Chat Prompt Completion Streaming ========");
StringBuilder chatPrompt = new("""
<message role="system">You are a librarian, expert about books</message>
<message role="user">Hi, I'm looking for book suggestions</message>
""");
var kernel = Kernel.CreateBuilder()
.AddAzureAIInferenceChatCompletion(
modelId: TestConfiguration.AzureAIInference.ChatModelId,
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
apiKey: TestConfiguration.AzureAIInference.ApiKey)
.Build();
var reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
reply = await StreamMessageOutputFromKernelAsync(kernel, chatPrompt.ToString());
Console.WriteLine(reply);
}
/// <summary>
/// This example demonstrates how the chat completion service streams text content.
/// It shows how to access the response update via StreamingChatMessageContent.Content property
/// and alternatively via the StreamingChatMessageContent.Items property.
/// </summary>
[Fact]
public async Task StreamTextFromChatAsync()
{
Console.WriteLine("======== Stream Text from Chat Content ========");
// Create chat completion service
var chatService = new ChatCompletionsClient(
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
credential: new Azure.AzureKeyCredential(TestConfiguration.AzureAIInference.ApiKey!))
.AsIChatClient(TestConfiguration.AzureAIInference.ChatModelId)
.AsChatCompletionService();
// Create chat history with initial system and user messages
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
// Start streaming chat based on the chat history
await foreach (StreamingChatMessageContent chatUpdate in chatService.GetStreamingChatMessageContentsAsync(chatHistory))
{
// Access the response update via StreamingChatMessageContent.Content property
Console.Write(chatUpdate.Content);
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
}
}
/// <summary>
/// Starts streaming chat with the chat completion service.
/// </summary>
/// <param name="chatCompletionService">The chat completion service instance.</param>
private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
{
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
OutputLastMessage(chatHistory);
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);
// First assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
OutputLastMessage(chatHistory);
// Second assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
}
/// <summary>
/// Outputs the chat history by streaming the message output from the kernel.
/// </summary>
/// <param name="kernel">The kernel instance.</param>
/// <param name="prompt">The prompt message.</param>
/// <returns>The full message output from the kernel.</returns>
private async Task<string> StreamMessageOutputFromKernelAsync(Kernel kernel, string prompt)
{
bool roleWritten = false;
string fullMessage = string.Empty;
await foreach (var chatUpdate in kernel.InvokePromptStreamingAsync<StreamingChatMessageContent>(prompt))
{
if (!roleWritten && chatUpdate.Role.HasValue)
{
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
roleWritten = true;
}
if (chatUpdate.Content is { Length: > 0 })
{
fullMessage += chatUpdate.Content;
Console.Write(chatUpdate.Content);
}
}
Console.WriteLine("\n------------------------");
return fullMessage;
}
}
@@ -0,0 +1,414 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Azure.AI.OpenAI.Chat;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using xRetry;
namespace ChatCompletion;
/// <summary>
/// This example demonstrates how to use Azure OpenAI Chat Completion with data.
/// </summary>
/// <value>
/// Set-up instructions:
/// <para>1. Upload the following content in Azure Blob Storage in a .txt file.</para>
/// <para>You can follow the steps here: <see href="https://learn.microsoft.com/en-us/azure/ai-services/openai/use-your-data-quickstart"/></para>
/// <para>
/// Emily and David, two passionate scientists, met during a research expedition to Antarctica.
/// Bonded by their love for the natural world and shared curiosity,
/// they uncovered a groundbreaking phenomenon in glaciology that could
/// potentially reshape our understanding of climate change.
/// </para>
/// 2. Set your secrets:
/// <para> dotnet user-secrets set "AzureAISearch:Endpoint" "https://... .search.windows.net"</para>
/// <para> dotnet user-secrets set "AzureAISearch:ApiKey" "{Key from your Search service resource}"</para>
/// <para> dotnet user-secrets set "AzureAISearch:IndexName" "..."</para>
/// </value>
public class AzureOpenAIWithData_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
{
[RetryFact(typeof(HttpOperationException))]
public async Task ExampleWithChatCompletionAsync()
{
Console.WriteLine("=== Example with Chat Completion ===");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey)
.Build();
var chatHistory = new ChatHistory();
// First question without previous context based on uploaded content.
var ask = "How did Emily and David meet?";
chatHistory.AddUserMessage(ask);
// Chat Completion example
var dataSource = GetAzureSearchDataSource();
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings { AzureChatDataSource = dataSource };
var chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
var chatMessage = await chatCompletion.GetChatMessageContentAsync(chatHistory, promptExecutionSettings);
var response = chatMessage.Content!;
// Output
// Ask: How did Emily and David meet?
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response}");
var citations = GetCitations(chatMessage);
OutputCitations(citations);
Console.WriteLine();
// Chat history maintenance
chatHistory.AddAssistantMessage(response);
// Second question based on uploaded content.
ask = "What are Emily and David studying?";
chatHistory.AddUserMessage(ask);
// Chat Completion Streaming example
Console.WriteLine($"Ask: {ask}");
Console.WriteLine("Response: ");
await foreach (var update in chatCompletion.GetStreamingChatMessageContentsAsync(chatHistory, promptExecutionSettings))
{
Console.Write(update);
var streamingCitations = GetCitations(update);
OutputCitations(streamingCitations);
}
Console.WriteLine(Environment.NewLine);
}
[RetryFact(typeof(HttpOperationException))]
public async Task ExampleWithKernelAsync()
{
Console.WriteLine("=== Example with Kernel ===");
var ask = "How did Emily and David meet?";
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey)
.Build();
var function = kernel.CreateFunctionFromPrompt("Question: {{$input}}");
var dataSource = GetAzureSearchDataSource();
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings { AzureChatDataSource = dataSource };
// First question without previous context based on uploaded content.
var response = await kernel.InvokeAsync(function, new(promptExecutionSettings) { ["input"] = ask });
// Output
// Ask: How did Emily and David meet?
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response.GetValue<string>()}");
Console.WriteLine();
// Second question based on uploaded content.
ask = "What are Emily and David studying?";
response = await kernel.InvokeAsync(function, new(promptExecutionSettings) { ["input"] = ask });
// Output
// Ask: What are Emily and David studying?
// Response: They are passionate scientists who study glaciology,
// a branch of geology that deals with the study of ice and its effects.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response.GetValue<string>()}");
Console.WriteLine();
}
/// <summary>
/// This example shows how to use Azure OpenAI Chat Completion with data and function calling.
/// Note: Using a data source and function calling is currently not supported in a single request. Enabling both features
/// will result in the function calling information being ignored and the operation behaving as if only the data source was provided.
/// More information about this limitation here: <see href="https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/openai/Azure.AI.OpenAI/README.md#use-your-own-data-with-azure-openai"/>.
/// To address this limitation, consider separating function calling and data source across multiple requests in your solution design.
/// The example demonstrates how to implement a retry mechanism for unanswered queries. If the current request uses an Azure Data Source, the logic retries using function calling, and vice versa.
/// </summary>
[Fact]
public async Task ExampleWithFunctionCallingAsync()
{
Console.WriteLine("=== Example with Function Calling ===");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
// Add retry filter.
// This filter will evaluate if the model provided the answer to user's question.
// If yes, it will return the result. Otherwise it will try to use Azure Data Source and function calling sequentially until
// the requested information is provided. If both sources doesn't contain the requested information, the model will explain that in response.
builder.Services.AddSingleton<IFunctionInvocationFilter, FunctionInvocationRetryFilter>();
var kernel = builder.Build();
// Import plugin.
kernel.ImportPluginFromType<DataPlugin>();
// Define response schema.
// The model evaluates its own answer and provides a boolean flag,
// which allows to understand whether the user's question was actually answered or not.
// Based on that, it's possible to make a decision whether the source of information should be changed or the response
// should be provided back to the user.
var responseSchema =
"""
{
"type": "object",
"properties": {
"Message": { "type": "string" },
"IsAnswered": { "type": "boolean" },
}
}
""";
// Define execution settings with response format and initial instructions.
var promptExecutionSettings = new AzureOpenAIPromptExecutionSettings
{
ResponseFormat = "json_object",
ChatSystemPrompt =
"Provide concrete answers to user questions. " +
"If you don't have the information - do not generate it, but respond accordingly. " +
$"Use following JSON schema for all the responses: {responseSchema}. "
};
// First question without previous context based on uploaded content.
var ask = "How did Emily and David meet?";
// The answer to the first question is expected to be fetched from Azure Data Source (in this example Azure AI Search).
// Azure Data Source is not enabled in initial execution settings, but is configured in retry filter.
var response = await kernel.InvokePromptAsync(ask, new(promptExecutionSettings));
var modelResult = ModelResult.Parse(response.ToString());
// Output
// Ask: How did Emily and David meet?
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica [doc1].
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {modelResult?.Message}");
ask = "Can I have Emily's and David's emails?";
// The answer to the second question is expected to be fetched from DataPlugin-GetEmails function using function calling.
// Function calling is not enabled in initial execution settings, but is configured in retry filter.
response = await kernel.InvokePromptAsync(ask, new(promptExecutionSettings));
modelResult = ModelResult.Parse(response.ToString());
// Output
// Ask: Can I have their emails?
// Response: Emily's email is emily@contoso.com and David's email is david@contoso.com.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {modelResult?.Message}");
}
/// <summary>
/// Initializes a new instance of the <see cref="AzureSearchChatDataSource"/> class.
/// </summary>
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
private static AzureSearchChatDataSource GetAzureSearchDataSource()
{
return new AzureSearchChatDataSource
{
Endpoint = new Uri(TestConfiguration.AzureAISearch.Endpoint),
Authentication = DataSourceAuthentication.FromApiKey(TestConfiguration.AzureAISearch.ApiKey),
IndexName = TestConfiguration.AzureAISearch.IndexName
};
}
/// <summary>
/// Returns a collection of <see cref="ChatCitation"/>.
/// </summary>
private static IList<ChatCitation> GetCitations(ChatMessageContent chatMessageContent)
{
var message = chatMessageContent.InnerContent as OpenAI.Chat.ChatCompletion;
var messageContext = message.GetMessageContext();
return messageContext.Citations;
}
/// <summary>
/// Returns a collection of <see cref="ChatCitation"/>.
/// </summary>
private static IList<ChatCitation>? GetCitations(StreamingChatMessageContent streamingContent)
{
var message = streamingContent.InnerContent as OpenAI.Chat.StreamingChatCompletionUpdate;
var messageContext = message?.GetMessageContext();
return messageContext?.Citations;
}
/// <summary>
/// Outputs a collection of <see cref="ChatCitation"/>.
/// </summary>
private void OutputCitations(IList<ChatCitation>? citations)
{
if (citations is not null)
{
Console.WriteLine("Citations:");
foreach (var citation in citations)
{
Console.WriteLine($"Chunk ID: {citation.ChunkId}");
Console.WriteLine($"Title: {citation.Title}");
Console.WriteLine($"File path: {citation.FilePath}");
Console.WriteLine($"URL: {citation.Url}");
Console.WriteLine($"Content: {citation.Content}");
}
}
}
/// <summary>
/// Filter which performs the retry logic to answer user's question using different sources.
/// Initially, if the model doesn't provide an answer, the filter will enable Azure Data Source and retry the same request.
/// If Azure Data Source doesn't contain the requested information, the filter will disable it and enable function calling instead.
/// If the answer is provided from the model itself or any source, it is returned back to the user.
/// </summary>
private sealed class FunctionInvocationRetryFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
// Retry logic for Azure Data Source and function calling is enabled only for Azure OpenAI prompt execution settings.
if (context.Arguments.ExecutionSettings is not null &&
context.Arguments.ExecutionSettings.TryGetValue(PromptExecutionSettings.DefaultServiceId, out var executionSettings) &&
executionSettings is AzureOpenAIPromptExecutionSettings azureOpenAIPromptExecutionSettings)
{
// Store the initial data source and function calling configuration to reset it after filter execution.
var initialAzureChatDataSource = azureOpenAIPromptExecutionSettings.AzureChatDataSource;
var initialFunctionChoiceBehavior = azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior;
// Track which source of information was used during the execution to try both sources sequentially.
var dataSourceUsed = initialAzureChatDataSource is not null;
var functionCallingUsed = initialFunctionChoiceBehavior is not null;
// Perform a request.
await next(context);
// Get and parse the result.
var result = context.Result.GetValue<string>();
var modelResult = ModelResult.Parse(result);
// If the model could not answer the question, then retry the request using an alternate technique:
// - If the Azure Data Source was used then disable it and enable function calling.
// - If function calling was used then disable it and enable the Azure Data Source.
while (modelResult?.IsAnswered is false || (!dataSourceUsed && !functionCallingUsed))
{
// If Azure Data Source wasn't used - enable it.
if (azureOpenAIPromptExecutionSettings.AzureChatDataSource is null)
{
var dataSource = GetAzureSearchDataSource();
// Since Azure Data Source is enabled, the function calling should be disabled,
// because they are not supported together.
azureOpenAIPromptExecutionSettings.AzureChatDataSource = dataSource;
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = null;
dataSourceUsed = true;
}
// Otherwise, if function calling wasn't used - enable it.
else if (azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior is null)
{
// Since function calling is enabled, the Azure Data Source should be disabled,
// because they are not supported together.
azureOpenAIPromptExecutionSettings.AzureChatDataSource = null;
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = FunctionChoiceBehavior.Auto();
functionCallingUsed = true;
}
// Perform a request.
await next(context);
// Get and parse the result.
result = context.Result.GetValue<string>();
modelResult = ModelResult.Parse(result);
}
// Reset prompt execution setting properties to the initial state.
azureOpenAIPromptExecutionSettings.AzureChatDataSource = initialAzureChatDataSource;
azureOpenAIPromptExecutionSettings.FunctionChoiceBehavior = initialFunctionChoiceBehavior;
}
// Otherwise, perform a default function invocation.
else
{
await next(context);
}
}
}
/// <summary>
/// Represents a model result with actual message and boolean flag which shows if user's question was answered or not.
/// </summary>
private sealed class ModelResult
{
public string Message { get; set; }
public bool IsAnswered { get; set; }
/// <summary>
/// Parses model result.
/// </summary>
public static ModelResult? Parse(string? result)
{
if (string.IsNullOrWhiteSpace(result))
{
return null;
}
// With response format as "json_object", sometimes the JSON response string is coming together with annotation.
// The following line normalizes the response string in order to deserialize it later.
var normalized = result
.Replace("```json", string.Empty)
.Replace("```", string.Empty);
return JsonSerializer.Deserialize<ModelResult>(normalized);
}
}
/// <summary>
/// Example of data plugin that provides a user information for demonstration purposes.
/// </summary>
private sealed class DataPlugin
{
private readonly Dictionary<string, string> _emails = new()
{
["Emily"] = "emily@contoso.com",
["David"] = "david@contoso.com",
};
[KernelFunction]
public List<string> GetEmails(List<string> users)
{
var emails = new List<string>();
foreach (var user in users)
{
if (this._emails.TryGetValue(user, out var email))
{
emails.Add(email);
}
}
return emails;
}
}
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using chat completion with Azure OpenAI API.
/// </summary>
public class AzureOpenAI_ChatCompletion(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
/// </summary>
[Fact]
public async Task ChatPromptAsync()
{
Console.WriteLine("======== Azure Open AI - Chat Completion ========");
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
StringBuilder chatPrompt = new("""
<message role="system">You are a librarian, expert about books</message>
<message role="user">Hi, I'm looking for book suggestions</message>
""");
var kernelBuilder = Kernel.CreateBuilder();
if (string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.ApiKey))
{
kernelBuilder.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
credentials: new DefaultAzureCredential(),
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
}
else
{
kernelBuilder.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
}
var kernel = kernelBuilder.Build();
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
chatPrompt.AppendLine($"<message role=\"assistant\"><![CDATA[{reply}]]></message>");
chatPrompt.AppendLine("<message role=\"user\">I love history and philosophy, I'd like to learn something new about Greece, any suggestion</message>");
reply = await kernel.InvokePromptAsync(chatPrompt.ToString());
Console.WriteLine(reply);
}
/// <summary>
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
/// </summary>
[Fact]
public async Task ServicePromptAsync()
{
Console.WriteLine("======== Azure Open AI - Chat Completion ========");
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
AzureOpenAIChatCompletionService chatCompletionService =
string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.ApiKey)
? new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
credentials: new DefaultAzureCredential(),
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
: new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);
// First assistant message
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
OutputLastMessage(chatHistory);
// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
OutputLastMessage(chatHistory);
// Second assistant message
reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
OutputLastMessage(chatHistory);
}
}
@@ -0,0 +1,131 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using streaming chat completion with Azure OpenAI API.
/// </summary>
public class AzureOpenAI_ChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// This example demonstrates chat completion streaming using Azure OpenAI.
/// </summary>
[Fact]
public Task StreamServicePromptAsync()
{
Console.WriteLine("======== Azure Open AI Chat Completion Streaming ========");
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
return this.StartStreamingChatAsync(chatCompletionService);
}
/// <summary>
/// This example demonstrates how the chat completion service streams text content.
/// It shows how to access the response update via StreamingChatMessageContent.Content property
/// and alternatively via the StreamingChatMessageContent.Items property.
/// </summary>
[Fact]
public async Task StreamServicePromptTextAsync()
{
Console.WriteLine("======== Azure Open AI Streaming Text ========");
// Create chat completion service
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
// Create chat history with initial system and user messages
ChatHistory chatHistory = new("You are a librarian, an expert on books.");
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions.");
chatHistory.AddUserMessage("I love history and philosophy. I'd like to learn something new about Greece, any suggestion?");
// Start streaming chat based on the chat history
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory))
{
// Access the response update via StreamingChatMessageContent.Content property
Console.Write(chatUpdate.Content);
// Alternatively, the response update can be accessed via the StreamingChatMessageContent.Items property
Console.Write(chatUpdate.Items.OfType<StreamingTextContent>().FirstOrDefault());
}
}
/// <summary>
/// This example demonstrates how the chat completion service streams raw function call content.
/// See <see cref="FunctionCalling.FunctionCalling.RunStreamingChatCompletionApiWithManualFunctionCallingAsync"/> for a sample demonstrating how to simplify
/// function call content building out of streamed function call updates using the <see cref="FunctionCallContentBuilder"/>.
/// </summary>
[Fact]
public async Task StreamFunctionCallContentAsync()
{
Console.WriteLine("======== Stream Function Call Content ========");
// Create chat completion service
AzureOpenAIChatCompletionService chatCompletionService = new(deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
// Create kernel with helper plugin.
Kernel kernel = new();
kernel.ImportPluginFromFunctions("HelperFunctions",
[
kernel.CreateFunctionFromMethod((string longTestString) => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
]);
// Create execution settings with manual function calling
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false) };
// Create chat history with initial user question
ChatHistory chatHistory = [];
chatHistory.AddUserMessage("Hi, what is the current time?");
// Start streaming chat based on the chat history
await foreach (StreamingChatMessageContent chatUpdate in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
{
// Getting list of function call updates requested by LLM
var streamingFunctionCallUpdates = chatUpdate.Items.OfType<StreamingFunctionCallUpdateContent>();
// Iterating over function call updates. Please use the unctionCallContentBuilder to simplify function call content building.
foreach (StreamingFunctionCallUpdateContent update in streamingFunctionCallUpdates)
{
Console.WriteLine($"Function call update: callId={update.CallId}, name={update.Name}, arguments={update.Arguments?.Replace("\n", "\\n")}, functionCallIndex={update.FunctionCallIndex}");
}
}
}
private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
{
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
OutputLastMessage(chatHistory);
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for book suggestions");
OutputLastMessage(chatHistory);
// First assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
// Second user message
chatHistory.AddUserMessage("I love history and philosophy, I'd like to learn something new about Greece, any suggestion?");
OutputLastMessage(chatHistory);
// Second assistant message
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
}
}
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using OpenAI.Chat;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using chat completion reasoning models with Azure OpenAI API.
/// </summary>
public class AzureOpenAI_ChatCompletionWithReasoning(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Sample showing how to use <see cref="Kernel"/> with chat completion and chat prompt syntax.
/// </summary>
[Fact]
public async Task ChatPromptWithReasoningAsync()
{
Console.WriteLine("======== Azure Open AI - Chat Completion with Reasoning ========");
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
// Create execution settings with high reasoning effort.
var executionSettings = new AzureOpenAIPromptExecutionSettings //OpenAIPromptExecutionSettings
{
// Flags Azure SDK to use the new token property.
SetNewMaxCompletionTokensEnabled = true,
MaxTokens = 2000,
// Note: reasoning effort is only available for reasoning models (at this moment o3-mini & o1 models)
ReasoningEffort = ChatReasoningEffortLevel.Low
};
// Create KernelArguments using the execution settings.
var kernelArgs = new KernelArguments(executionSettings);
StringBuilder chatPrompt = new("""
<message role="developer">You are an expert software engineer, specialized in the Semantic Kernel SDK and NET framework</message>
<message role="user">Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop .</message>
""");
// Invoke the prompt with high reasoning effort.
var reply = await kernel.InvokePromptAsync(chatPrompt.ToString(), kernelArgs);
Console.WriteLine(reply);
}
/// <summary>
/// Sample showing how to use <see cref="IChatCompletionService"/> directly with a <see cref="ChatHistory"/>.
/// </summary>
[Fact]
public async Task ServicePromptWithReasoningAsync()
{
Console.WriteLine("======== Azure Open AI - Chat Completion with Azure Default Credential with Reasoning ========");
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
IChatCompletionService chatCompletionService = new AzureOpenAIChatCompletionService(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
// Create execution settings with high reasoning effort.
var executionSettings = new AzureOpenAIPromptExecutionSettings
{
// Flags Azure SDK to use the new token property.
SetNewMaxCompletionTokensEnabled = true,
MaxTokens = 2000,
// Note: reasoning effort is only available for reasoning models (at this moment o3-mini & o1 models)
ReasoningEffort = ChatReasoningEffortLevel.Low
};
// Create a ChatHistory and add messages.
var chatHistory = new ChatHistory();
chatHistory.AddDeveloperMessage(
"You are an expert software engineer, specialized in the Semantic Kernel SDK and .NET framework.");
chatHistory.AddUserMessage(
"Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop.");
// Instead of a prompt string, call GetChatMessageContentAsync with the chat history.
var reply = await chatCompletionService.GetChatMessageContentAsync(
chatHistory: chatHistory,
executionSettings: executionSettings);
Console.WriteLine(reply);
}
}
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using System.ClientModel.Primitives;
using Azure.AI.OpenAI;
using Microsoft.SemanticKernel;
#pragma warning disable CA5399 // HttpClient is created without enabling CheckCertificateRevocationList
namespace ChatCompletion;
/// <summary>
/// This example shows a way of using a Custom HttpClient and HttpHandler with Azure OpenAI Connector to capture
/// the request Uri and Headers for each request.
/// </summary>
public sealed class AzureOpenAI_CustomClient(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task UsingCustomHttpClientWithAzureOpenAI()
{
Assert.NotNull(TestConfiguration.AzureOpenAI.Endpoint);
Assert.NotNull(TestConfiguration.AzureOpenAI.ChatDeploymentName);
Assert.NotNull(TestConfiguration.AzureOpenAI.ApiKey);
Console.WriteLine($"======== Azure Open AI - {nameof(UsingCustomHttpClientWithAzureOpenAI)} ========");
// Create an HttpClient and include your custom header(s)
using var myCustomHttpHandler = new MyCustomClientHttpHandler(Output);
using var myCustomClient = new HttpClient(handler: myCustomHttpHandler);
myCustomClient.DefaultRequestHeaders.Add("My-Custom-Header", "My Custom Value");
// Configure AzureOpenAIClient to use the customized HttpClient
var clientOptions = new AzureOpenAIClientOptions
{
Transport = new HttpClientPipelineTransport(myCustomClient),
NetworkTimeout = TimeSpan.FromSeconds(30),
RetryPolicy = new ClientRetryPolicy()
};
var customClient = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey), clientOptions);
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, customClient)
.Build();
// Load semantic plugin defined with prompt templates
string folder = RepoFiles.SamplePluginsPath();
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin"));
// Run
var result = await kernel.InvokeAsync(
kernel.Plugins["FunPlugin"]["Excuses"],
new() { ["input"] = "I have no homework" }
);
Console.WriteLine(result.GetValue<string>());
myCustomClient.Dispose();
}
/// <summary>
/// Normally you would use a custom HttpClientHandler to add custom logic to your custom http client
/// This uses the ITestOutputHelper to write the requested URI to the test output
/// </summary>
/// <param name="output">The <see cref="ITestOutputHelper"/> to write the requested URI to the test output </param>
private sealed class MyCustomClientHttpHandler(ITestOutputHelper output) : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
output.WriteLine($"Requested URI: {request.RequestUri}");
request.Headers.Where(h => h.Key != "Authorization")
.ToList()
.ForEach(h => output.WriteLine($"{h.Key}: {string.Join(", ", h.Value)}"));
output.WriteLine("--------------------------------");
// Add custom logic here
return await base.SendAsync(request, cancellationToken);
}
}
}
@@ -0,0 +1,115 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace ChatCompletion;
// The following example shows how to use Chat History with Author identity associated with each chat message.
public class ChatHistoryAuthorName(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Flag to force usage of OpenAI configuration if both <see cref="TestConfiguration.OpenAI"/>
/// and <see cref="TestConfiguration.AzureOpenAI"/> are defined.
/// If 'false', Azure takes precedence.
/// </summary>
/// <remarks>
/// NOTE: Retrieval tools is not currently available on Azure.
/// </remarks>
private new const bool ForceOpenAI = true;
private static readonly OpenAIPromptExecutionSettings s_executionSettings =
new()
{
FrequencyPenalty = 0,
PresencePenalty = 0,
Temperature = 1,
TopP = 0.5,
};
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task CompletionIdentityAsync(bool withName)
{
Console.WriteLine("======== Completion Identity ========");
IChatCompletionService chatService = CreateCompletionService();
ChatHistory chatHistory = CreateHistory(withName);
WriteMessages(chatHistory);
WriteMessages(await chatService.GetChatMessageContentsAsync(chatHistory, s_executionSettings), chatHistory);
ValidateMessages(chatHistory, withName);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task StreamingIdentityAsync(bool withName)
{
Console.WriteLine("======== Completion Identity ========");
IChatCompletionService chatService = CreateCompletionService();
ChatHistory chatHistory = CreateHistory(withName);
var content = await chatHistory.AddStreamingMessageAsync(chatService.GetStreamingChatMessageContentsAsync(chatHistory, s_executionSettings).Cast<OpenAIStreamingChatMessageContent>()).ToArrayAsync();
WriteMessages(chatHistory);
ValidateMessages(chatHistory, withName);
}
private static ChatHistory CreateHistory(bool withName)
{
return
[
new ChatMessageContent(AuthorRole.System, "Write one paragraph in response to the user that rhymes") { AuthorName = withName ? "Echo" : null },
new ChatMessageContent(AuthorRole.User, "Why is AI awesome") { AuthorName = withName ? "Ralph" : null },
];
}
private void ValidateMessages(ChatHistory chatHistory, bool expectName)
{
foreach (var message in chatHistory)
{
if (expectName && message.Role != AuthorRole.Assistant)
{
Assert.NotNull(message.AuthorName);
}
else
{
Assert.Null(message.AuthorName);
}
}
}
private void WriteMessages(IReadOnlyList<ChatMessageContent> messages, ChatHistory? history = null)
{
foreach (var message in messages)
{
Console.WriteLine($"# {message.Role}:{message.AuthorName ?? "?"} - {message.Content ?? "-"}");
}
history?.AddRange(messages);
}
private static IChatCompletionService CreateCompletionService()
{
return
ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
new OpenAIChatCompletionService(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey) :
new AzureOpenAIChatCompletionService(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
}
}
@@ -0,0 +1,253 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace ChatCompletion;
/// <summary>
/// This example shows how to access <see cref="ChatHistory"/> object in Semantic Kernel functions using
/// <see cref="Kernel.Data"/> and <see cref="KernelArguments"/>.
/// This scenario can be useful with auto function calling,
/// when logic in SK functions depends on results from previous messages in the same chat history.
/// </summary>
public sealed class ChatHistoryInFunctions(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using <see cref="Kernel.Data"/> property.
/// This approach should be used with caution for cases when Kernel is registered in application as singleton.
/// For singleton Kernel, check examples <see cref="UsingKernelArgumentsAndFilterOption1Async"/> and <see cref="UsingKernelArgumentsAndFilterOption2Async"/>.
/// </summary>
[Fact]
public async Task UsingKernelDataAsync()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
// Get chat completion service.
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// Initialize chat history with prompt.
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage("I want to get an information about featured products, product reviews and daily summary.");
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Set chat history in kernel data to access it in a function.
kernel.Data[nameof(ChatHistory)] = chatHistory;
// Send a request.
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
// Each function will receive a greater number of messages in chat history, because chat history is populated
// with results of previous functions.
Console.WriteLine($"Result: {result}");
// Output:
// GetFeaturedProducts - Chat History Message Count: 2
// GetProductReviews - Chat History Message Count: 3
// GetDailySalesSummary - Chat History Message Count: 4
// Result: Here's the information you requested...
}
/// <summary>
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
/// The plugin has access to <see cref="KernelArguments"/>, so it's possible to find a chat history in arguments by property name.
/// </summary>
[Fact]
public async Task UsingKernelArgumentsAndFilterOption1Async()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
// Add filter.
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Send a request.
var result = await kernel.InvokePromptAsync("I want to get an information about featured products, product reviews and daily summary.", new(executionSettings));
// Each function will receive a greater number of messages in chat history, because chat history is populated
// with results of previous functions.
Console.WriteLine($"Result: {result}");
// Output:
// GetFeaturedProducts - Chat History Message Count: 2
// GetProductReviews - Chat History Message Count: 3
// GetDailySalesSummary - Chat History Message Count: 4
// Result: Here's the information you requested...
}
/// <summary>
/// This method passes an instance of <see cref="ChatHistory"/> to SK function using
/// <see cref="KernelArguments"/> and <see cref="IAutoFunctionInvocationFilter"/> filter.
/// The plugin has access to <see cref="ChatHistory"/> directly, since it's automatically injected from <see cref="KernelArguments"/>
/// into the function by argument name.
/// </summary>
[Fact]
public async Task UsingKernelArgumentsAndFilterOption2Async()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new EmailPlugin(this.Output));
// Add filter.
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Send a request.
var result = await kernel.InvokePromptAsync("Send email to test@contoso.com", new(executionSettings));
Console.WriteLine($"Result: {result}");
// Output:
// SendEmail - Chat History Message Count: 2
// Result: Email has been sent to test@contoso.com.
}
#region private
/// <summary>
/// Implementation of <see cref="IAutoFunctionInvocationFilter"/> to set chat history in <see cref="KernelArguments"/>
/// before invoking a function.
/// </summary>
private sealed class AutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
// Set chat history in kernel arguments.
if (context.Arguments is not null)
{
// nameof(ChatHistory) is used for demonstration purposes.
// Any name can be used here, as long as it is effective for the intended purpose.
// However, the same name must be used when retrieving chat history from the KernelArguments instance
// or when the ChatHistory parameter is directly injected into a function.
context.Arguments[nameof(ChatHistory)] = context.ChatHistory;
}
// Invoke next filter in pipeline or function.
await next(context);
}
}
/// <summary>
/// Data plugin for demonstration purposes, where methods accept <see cref="Kernel"/> and <see cref="KernelArguments"/>
/// as parameters.
/// </summary>
private sealed class DataPlugin(ITestOutputHelper output)
{
[KernelFunction]
public List<string> GetFeaturedProducts(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetFeaturedProducts)} - Chat History Message Count: {chatHistory.Count}");
}
return ["Laptop", "Smartphone", "Smartwatch"];
}
[KernelFunction]
public Dictionary<string, List<string>> GetProductReviews(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetProductReviews)} - Chat History Message Count: {chatHistory.Count}");
}
return new()
{
["Laptop"] = ["Excellent performance!", "Battery life could be better."],
["Smartphone"] = ["Amazing camera!", "Very responsive."],
["Smartwatch"] = ["Stylish design", "Could use more apps."],
};
}
[KernelFunction]
public string GetDailySalesSummary(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetDailySalesSummary)} - Chat History Message Count: {chatHistory.Count}");
}
const int OrdersProcessed = 50;
const decimal TotalRevenue = 12345.67m;
return $"Today's Sales: {OrdersProcessed} orders processed, total revenue: ${TotalRevenue}.";
}
private static ChatHistory? GetChatHistory(IDictionary<string, object?> data)
{
if (data.TryGetValue(nameof(ChatHistory), out object? chatHistoryObj) &&
chatHistoryObj is ChatHistory chatHistory)
{
return chatHistory;
}
return null;
}
}
/// <summary>
/// Email plugin for demonstration purposes, where method accepts <see cref="ChatHistory"/> as parameter.
/// </summary>
private sealed class EmailPlugin(ITestOutputHelper output)
{
[KernelFunction]
public string SendEmail(string to, ChatHistory? chatHistory = null)
{
if (chatHistory is not null)
{
output.WriteLine($"{nameof(SendEmail)} - Chat History Message Count: {chatHistory.Count}");
}
// Simulate the email-sending process by notifying the AI model that the email was sent.
return $"Email has been sent to {to}";
}
}
/// <summary>
/// Helper method to initialize <see cref="Kernel"/>.
/// </summary>
private static Kernel GetKernel()
{
return Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
}
#endregion
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// Extensions methods for <see cref="IChatCompletionService"/>
/// </summary>
internal static class ChatCompletionServiceExtensions
{
/// <summary>
/// Adds an wrapper to an instance of <see cref="IChatCompletionService"/> which will use
/// the provided instance of <see cref="IChatHistoryReducer"/> to reduce the size of
/// the <see cref="ChatHistory"/> before sending it to the model.
/// </summary>
/// <param name="service">Instance of <see cref="IChatCompletionService"/></param>
/// <param name="reducer">Instance of <see cref="IChatHistoryReducer"/></param>
public static IChatCompletionService UsingChatHistoryReducer(this IChatCompletionService service, IChatHistoryReducer reducer)
{
return new ChatCompletionServiceWithReducer(service, reducer);
}
}
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Runtime.CompilerServices;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// Instance of <see cref="IChatCompletionService"/> which will invoke a delegate
/// to reduce the size of the <see cref="ChatHistory"/> before sending it to the model.
/// </summary>
public sealed class ChatCompletionServiceWithReducer(IChatCompletionService service, IChatHistoryReducer reducer) : IChatCompletionService
{
private static IReadOnlyDictionary<string, object?> EmptyAttributes { get; } = new Dictionary<string, object?>();
public IReadOnlyDictionary<string, object?> Attributes => EmptyAttributes;
/// <inheritdoc/>
public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
CancellationToken cancellationToken = default)
{
var reducedMessages = await reducer.ReduceAsync(chatHistory, cancellationToken).ConfigureAwait(false);
var reducedHistory = reducedMessages is null ? chatHistory : new ChatHistory(reducedMessages);
return await service.GetChatMessageContentsAsync(reducedHistory, executionSettings, kernel, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var reducedMessages = await reducer.ReduceAsync(chatHistory, cancellationToken).ConfigureAwait(false);
var history = reducedMessages is null ? chatHistory : new ChatHistory(reducedMessages);
var messages = service.GetStreamingChatMessageContentsAsync(history, executionSettings, kernel, cancellationToken);
await foreach (var message in messages)
{
yield return message;
}
}
}
@@ -0,0 +1,147 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.ML.Tokenizers;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// Extension methods for <see cref="ChatHistory"/>."/>
/// </summary>
internal static class ChatHistoryExtensions
{
private static readonly Tokenizer s_tokenizer = TiktokenTokenizer.CreateForModel("gpt-4");
/// <summary>
/// Returns the system prompt from the chat history.
/// </summary>
/// <remarks>
/// For simplicity only a single system message is supported in these examples.
/// </remarks>
internal static ChatMessageContent? GetSystemMessage(this IReadOnlyList<ChatMessageContent> chatHistory)
{
return chatHistory.FirstOrDefault(m => m.Role == AuthorRole.System);
}
/// <summary>
/// Extract a range of messages from the provided <see cref="ChatHistory"/>.
/// </summary>
/// <param name="chatHistory">The source history</param>
/// <param name="startIndex">The index of the first messageContent to extract</param>
/// <param name="endIndex">The index of the first messageContent to extract, if null extract up to the end of the chat history</param>
/// <param name="systemMessage">An optional system messageContent to include</param>
/// <param name="summaryMessage">An optional summary messageContent to include</param>
/// <param name="messageFilter">An optional message filter</param>
public static IEnumerable<ChatMessageContent> Extract(
this IReadOnlyList<ChatMessageContent> chatHistory,
int startIndex,
int? endIndex = null,
ChatMessageContent? systemMessage = null,
ChatMessageContent? summaryMessage = null,
Func<ChatMessageContent, bool>? messageFilter = null)
{
endIndex ??= chatHistory.Count - 1;
if (startIndex > endIndex)
{
yield break;
}
if (systemMessage is not null)
{
yield return systemMessage;
}
if (summaryMessage is not null)
{
yield return summaryMessage;
}
for (int index = startIndex; index <= endIndex; ++index)
{
var messageContent = chatHistory[index];
if (messageFilter?.Invoke(messageContent) ?? false)
{
continue;
}
yield return messageContent;
}
}
/// <summary>
/// Compute the index truncation where truncation should begin using the current truncation threshold.
/// </summary>
/// <param name="chatHistory">The source history.</param>
/// <param name="truncatedSize">Truncated size.</param>
/// <param name="truncationThreshold">Truncation threshold.</param>
/// <param name="hasSystemMessage">Flag indicating whether or not the chat history contains a system messageContent</param>
public static int ComputeTruncationIndex(this IReadOnlyList<ChatMessageContent> chatHistory, int truncatedSize, int truncationThreshold, bool hasSystemMessage)
{
if (chatHistory.Count <= truncationThreshold)
{
return -1;
}
// Compute the index of truncation target
var truncationIndex = chatHistory.Count - (truncatedSize - (hasSystemMessage ? 1 : 0));
// Skip function related content
while (truncationIndex < chatHistory.Count)
{
if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent))
{
truncationIndex++;
}
else
{
break;
}
}
return truncationIndex;
}
/// <summary>
/// Add a system messageContent to the chat history
/// </summary>
/// <param name="chatHistory">Chat history instance</param>
/// <param name="content">Message content</param>
public static void AddSystemMessageWithTokenCount(this ChatHistory chatHistory, string content)
{
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
{
["TokenCount"] = s_tokenizer.CountTokens(content)
};
chatHistory.AddMessage(AuthorRole.System, content, metadata: metadata);
}
/// <summary>
/// Add a user messageContent to the chat history
/// </summary>
/// <param name="chatHistory">Chat history instance</param>
/// <param name="content">Message content</param>
public static void AddUserMessageWithTokenCount(this ChatHistory chatHistory, string content)
{
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
{
["TokenCount"] = s_tokenizer.CountTokens(content)
};
chatHistory.AddMessage(AuthorRole.User, content, metadata: metadata);
}
/// <summary>
/// Add a assistant messageContent to the chat history
/// </summary>
/// <param name="chatHistory">Chat history instance</param>
/// <param name="content">Message content</param>
public static void AddAssistantMessageWithTokenCount(this ChatHistory chatHistory, string content)
{
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
{
["TokenCount"] = s_tokenizer.CountTokens(content)
};
chatHistory.AddMessage(AuthorRole.Assistant, content, metadata: metadata);
}
}
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// Implementation of <see cref="IChatHistoryReducer"/> which trim to the specified max token count.
/// </summary>
/// <remarks>
/// This reducer requires that the ChatMessageContent.MetaData contains a TokenCount property.
/// </remarks>
public sealed class ChatHistoryMaxTokensReducer : IChatHistoryReducer
{
private readonly int _maxTokenCount;
/// <summary>
/// Creates a new instance of <see cref="ChatHistoryMaxTokensReducer"/>.
/// </summary>
/// <param name="maxTokenCount">Max token count to send to the model.</param>
public ChatHistoryMaxTokensReducer(int maxTokenCount)
{
if (maxTokenCount <= 0)
{
throw new ArgumentException("Maximum token count must be greater than zero.", nameof(maxTokenCount));
}
this._maxTokenCount = maxTokenCount;
}
/// <inheritdoc/>
public Task<IEnumerable<ChatMessageContent>?> ReduceAsync(IReadOnlyList<ChatMessageContent> chatHistory, CancellationToken cancellationToken = default)
{
var systemMessage = chatHistory.GetSystemMessage();
var truncationIndex = ComputeTruncationIndex(chatHistory, systemMessage);
IEnumerable<ChatMessageContent>? truncatedHistory = null;
if (truncationIndex > 0)
{
truncatedHistory = chatHistory.Extract(truncationIndex, systemMessage: systemMessage);
}
return Task.FromResult<IEnumerable<ChatMessageContent>?>(truncatedHistory);
}
#region private
/// <summary>
/// Compute the index truncation where truncation should begin using the current truncation threshold.
/// </summary>
/// <param name="chatHistory">Chat history to be truncated.</param>
/// <param name="systemMessage">The system message</param>
private int ComputeTruncationIndex(IReadOnlyList<ChatMessageContent> chatHistory, ChatMessageContent? systemMessage)
{
var truncationIndex = -1;
var totalTokenCount = (int)(systemMessage?.Metadata?["TokenCount"] ?? 0);
for (int i = chatHistory.Count - 1; i >= 0; i--)
{
truncationIndex = i;
var tokenCount = (int)(chatHistory[i].Metadata?["TokenCount"] ?? 0);
if (tokenCount + totalTokenCount > this._maxTokenCount)
{
break;
}
totalTokenCount += tokenCount;
}
// Skip function related content
while (truncationIndex < chatHistory.Count)
{
if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent))
{
truncationIndex++;
}
else
{
break;
}
}
return truncationIndex;
}
#endregion
}
@@ -0,0 +1,135 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Runtime.CompilerServices;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// Unit tests for <see cref="IChatHistoryReducer"/> implementations.
/// </summary>
public class ChatHistoryReducerTests(ITestOutputHelper output) : BaseTest(output)
{
[Theory]
[InlineData(3, null, null, 100, 0)]
[InlineData(3, "SystemMessage", null, 100, 0)]
[InlineData(6, null, null, 100, 4)]
[InlineData(6, "SystemMessage", null, 100, 5)]
[InlineData(6, null, new int[] { 1 }, 100, 4)]
[InlineData(6, "SystemMessage", new int[] { 2 }, 100, 4)]
public async Task VerifyMaxTokensChatHistoryReducerAsync(int messageCount, string? systemMessage, int[]? functionCallIndexes, int maxTokens, int expectedSize)
{
// Arrange
var chatHistory = CreateHistoryWithUserInput(messageCount, systemMessage, functionCallIndexes, true);
var reducer = new ChatHistoryMaxTokensReducer(maxTokens);
// Act
var reducedHistory = await reducer.ReduceAsync(chatHistory);
// Assert
VerifyReducedHistory(reducedHistory, ComputeExpectedMessages(chatHistory, expectedSize));
}
private static void VerifyReducedHistory(IEnumerable<ChatMessageContent>? reducedHistory, ChatMessageContent[]? expectedMessages)
{
if (expectedMessages is null)
{
Assert.Null(reducedHistory);
return;
}
Assert.NotNull(reducedHistory);
ChatMessageContent[] messages = reducedHistory.ToArray();
Assert.Equal(expectedMessages.Length, messages.Length);
Assert.Equal(expectedMessages, messages);
}
private static ChatMessageContent[]? ComputeExpectedMessages(ChatHistory chatHistory, int expectedSize)
{
if (expectedSize == 0)
{
return null;
}
var systemMessage = chatHistory.GetSystemMessage();
var count = expectedSize - ((systemMessage is null) ? 0 : 1);
var expectedMessages = chatHistory.TakeLast<ChatMessageContent>(count).ToArray();
if (systemMessage is not null)
{
expectedMessages = [systemMessage, .. expectedMessages];
}
return expectedMessages;
}
/// <summary>
/// Create an alternating list of user and assistant messages.
/// Function content is optionally injected at specified indexes.
/// </summary>
private static ChatHistory CreateHistoryWithUserInput(int messageCount, string? systemMessage = null, int[]? functionCallIndexes = null, bool includeTokenCount = false)
{
var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
var chatHistory = new ChatHistory();
if (systemMessage is not null)
{
chatHistory.AddSystemMessageWithTokenCount(systemMessage);
}
for (int index = 0; index < messageCount; ++index)
{
if (index % 2 == 1)
{
if (includeTokenCount)
{
chatHistory.AddAssistantMessageWithTokenCount($"Assistant response:{index} - {text}");
}
else
{
chatHistory.AddAssistantMessage($"Assistant response:{index} - {text}");
}
}
else
{
if (includeTokenCount)
{
chatHistory.AddUserMessageWithTokenCount($"User input:{index} - {text}");
}
else
{
chatHistory.AddUserMessageWithTokenCount($"User input:{index} - {text}");
}
}
if (functionCallIndexes is not null && functionCallIndexes.Contains(index))
{
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?>
{
["TokenCount"] = 10
};
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, [new FunctionCallContent($"Function call 1: {index}")], metadata: metadata));
chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, [new FunctionResultContent($"Function call 1: {index}")], metadata: metadata));
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, [new FunctionCallContent($"Function call 2: {index}")], metadata: metadata));
chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, [new FunctionResultContent($"Function call 2: {index}")], metadata: metadata));
}
}
return chatHistory;
}
private sealed class FakeChatCompletionService(string result) : IChatCompletionService
{
public IReadOnlyDictionary<string, object?> Attributes { get; } = new Dictionary<string, object?>();
public Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
return Task.FromResult<IReadOnlyList<ChatMessageContent>>([new(AuthorRole.Assistant, result)]);
}
#pragma warning disable IDE0036 // Order modifiers
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
#pragma warning restore IDE0036 // Order modifiers
{
yield return new StreamingChatMessageContent(AuthorRole.Assistant, result);
}
}
}
@@ -0,0 +1,129 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
public class ChatHistorySerialization(ITestOutputHelper output) : BaseTest(output)
{
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
/// <summary>
/// Demonstrates how to serialize and deserialize <see cref="ChatHistory"/> class
/// with <see cref="ChatMessageContent"/> having SK various content types as items.
/// </summary>
[Fact]
public async Task SerializeChatHistoryWithSKContentTypesAsync()
{
int[] data = [1, 2, 3];
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change.")
{
Items =
[
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."),
new ImageContent(new Uri("https://fake-random-test-host:123")),
new BinaryContent(new BinaryData(data), "application/octet-stream"),
new AudioContent(new BinaryData(data), "application/octet-stream")
]
};
var chatHistory = new ChatHistory([message]);
var chatHistoryJson = JsonSerializer.Serialize(chatHistory, s_options);
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson);
var deserializedMessage = deserializedHistory!.Single();
Console.WriteLine($"Content: {deserializedMessage.Content}");
Console.WriteLine($"Role: {deserializedMessage.Role.Label}");
Console.WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}");
Console.WriteLine($"Image content: {(deserializedMessage.Items![1]! as ImageContent)!.Uri}");
Console.WriteLine($"Binary content: {Encoding.UTF8.GetString((deserializedMessage.Items![2]! as BinaryContent)!.Data!.Value.Span)}");
Console.WriteLine($"Audio content: {Encoding.UTF8.GetString((deserializedMessage.Items![3]! as AudioContent)!.Data!.Value.Span)}");
Console.WriteLine($"JSON:\n{chatHistoryJson}");
}
/// <summary>
/// Shows how to serialize and deserialize <see cref="ChatHistory"/> class with <see cref="ChatMessageContent"/> having custom content type as item.
/// </summary>
[Fact]
public void SerializeChatWithHistoryWithCustomContentType()
{
var message = new ChatMessageContent(AuthorRole.User, "Describe the factors contributing to climate change.")
{
Items =
[
new TextContent("Discuss the potential long-term consequences for the Earth's ecosystem as well."),
new CustomContent("Some custom content"),
]
};
var chatHistory = new ChatHistory([message]);
// The custom resolver should be used to serialize and deserialize the chat history with custom .
var options = new JsonSerializerOptions
{
TypeInfoResolver = new CustomResolver(),
WriteIndented = true,
};
var chatHistoryJson = JsonSerializer.Serialize(chatHistory, options);
var deserializedHistory = JsonSerializer.Deserialize<ChatHistory>(chatHistoryJson, options);
var deserializedMessage = deserializedHistory!.Single();
Console.WriteLine($"Content: {deserializedMessage.Content}");
Console.WriteLine($"Role: {deserializedMessage.Role.Label}");
Console.WriteLine($"Text content: {(deserializedMessage.Items![0]! as TextContent)!.Text}");
Console.WriteLine($"Custom content: {(deserializedMessage.Items![1]! as CustomContent)!.Content}");
Console.WriteLine($"JSON:\n{chatHistoryJson}");
}
private sealed class CustomContent(string content) : KernelContent(content)
{
public string Content { get; } = content;
}
/// <summary>
/// The TypeResolver is used to serialize and deserialize custom content types polymorphically.
/// For more details, refer to the <see href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-8-0"/> article.
/// </summary>
private sealed class CustomResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
if (jsonTypeInfo.Type != typeof(KernelContent))
{
return jsonTypeInfo;
}
// It's possible to completely override the polymorphic configuration specified in the KernelContent class
// by using the '=' assignment operator instead of the ??= compound assignment one in the line below.
jsonTypeInfo.PolymorphismOptions ??= new JsonPolymorphismOptions();
// Add custom content type to the list of derived types declared on KernelContent class.
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(typeof(CustomContent), "customContent"));
// Override type discriminator declared on KernelContent class as "$type", if needed.
jsonTypeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = "name";
return jsonTypeInfo;
}
}
}
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
namespace ChatCompletion;
// These examples show how to use a custom HttpClient with SK connectors.
public class Connectors_CustomHttpClient(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Demonstrates the usage of the default HttpClient provided by the SK SDK.
/// </summary>
[Fact]
public void UseDefaultHttpClient()
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey) // If you need to use the default HttpClient from the SK SDK, simply omit the argument for the httpMessageInvoker parameter.
.Build();
}
/// <summary>
/// Demonstrates the usage of a custom HttpClient.
/// </summary>
[Fact]
public void UseCustomHttpClient()
{
using var httpClient = new HttpClient();
// If you need to use a custom HttpClient, simply pass it as an argument for the httpClient parameter.
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ModelId,
apiKey: TestConfiguration.OpenAI.ApiKey,
httpClient: httpClient)
.Build();
}
}
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace ChatCompletion;
/// <summary>
/// This example shows how you can use Streaming with Kernel.
/// </summary>
/// <param name="output"></param>
public class Connectors_KernelStreaming(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task RunAsync()
{
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string chatModelId = TestConfiguration.AzureOpenAI.ChatModelId;
string endpoint = TestConfiguration.AzureOpenAI.Endpoint;
if (apiKey is null || chatDeploymentName is null || chatModelId is null || endpoint is null)
{
Console.WriteLine("Azure endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
return;
}
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: chatDeploymentName,
endpoint: endpoint,
serviceId: "AzureOpenAIChat",
apiKey: apiKey,
modelId: chatModelId)
.Build();
var funnyParagraphFunction = kernel.CreateFunctionFromPrompt("Write a funny paragraph about streaming", new OpenAIPromptExecutionSettings() { MaxTokens = 100, Temperature = 0.4, TopP = 1 });
var roleDisplayed = false;
Console.WriteLine("\n=== Prompt Function - Streaming ===\n");
string fullContent = string.Empty;
// Streaming can be of any type depending on the underlying service the function is using.
await foreach (var update in kernel.InvokeStreamingAsync<OpenAIStreamingChatMessageContent>(funnyParagraphFunction))
{
// You will be always able to know the type of the update by checking the Type property.
if (!roleDisplayed && update.Role.HasValue)
{
Console.WriteLine($"Role: {update.Role}");
fullContent += $"Role: {update.Role}\n";
roleDisplayed = true;
}
if (update.Content is { Length: > 0 })
{
fullContent += update.Content;
Console.Write(update.Content);
}
}
Console.WriteLine("\n------ Streamed Content ------\n");
Console.WriteLine(fullContent);
}
}
@@ -0,0 +1,185 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
namespace ChatCompletion;
public class Connectors_WithMultipleLLMs(ITestOutputHelper output) : BaseTest(output)
{
private const string ChatPrompt = "Hello AI, what can you do for me?";
private static Kernel BuildKernel()
{
return Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
serviceId: "AzureOpenAIChat",
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey,
serviceId: "OpenAIChat")
.Build();
}
/// <summary>
/// Shows how to invoke a prompt and specify the service id of the preferred AI service. When the prompt is executed the AI Service with the matching service id will be selected.
/// </summary>
/// <param name="serviceId">Service Id</param>
[Theory]
[InlineData("AzureOpenAIChat")]
public async Task InvokePromptByServiceIdAsync(string serviceId)
{
var kernel = BuildKernel();
Console.WriteLine($"======== Service Id: {serviceId} ========");
var result = await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ServiceId = serviceId }));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to invoke a prompt and specify the model id of the preferred AI service. When the prompt is executed the AI Service with the matching model id will be selected.
/// </summary>
[Fact]
private async Task InvokePromptByModelIdAsync()
{
var modelId = TestConfiguration.OpenAI.ChatModelId;
var kernel = BuildKernel();
Console.WriteLine($"======== Model Id: {modelId} ========");
var result = await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings() { ModelId = modelId }));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to invoke a prompt and specify the service ids of the preferred AI services.
/// When the prompt is executed the AI Service will be selected based on the order of the provided service ids.
/// </summary>
[Fact]
public async Task InvokePromptFunctionWithFirstMatchingServiceIdAsync()
{
string[] serviceIds = ["NotFound", "AzureOpenAIChat", "OpenAIChat"];
var kernel = BuildKernel();
Console.WriteLine($"======== Service Ids: {string.Join(", ", serviceIds)} ========");
var result = await kernel.InvokePromptAsync(ChatPrompt, new(serviceIds.Select(serviceId => new PromptExecutionSettings { ServiceId = serviceId })));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to invoke a prompt and specify the model ids of the preferred AI services.
/// When the prompt is executed the AI Service will be selected based on the order of the provided model ids.
/// </summary>
[Fact]
public async Task InvokePromptFunctionWithFirstMatchingModelIdAsync()
{
string[] modelIds = ["gpt-4-1106-preview", TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.OpenAI.ChatModelId];
var kernel = BuildKernel();
Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
var result = await kernel.InvokePromptAsync(ChatPrompt, new(modelIds.Select((modelId, index) => new PromptExecutionSettings { ServiceId = $"service-{index}", ModelId = modelId })));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to create a KernelFunction from a prompt and specify the service ids of the preferred AI services.
/// When the function is invoked the AI Service will be selected based on the order of the provided service ids.
/// </summary>
[Fact]
public async Task InvokePreconfiguredFunctionWithFirstMatchingServiceIdAsync()
{
string[] serviceIds = ["NotFound", "AzureOpenAIChat", "OpenAIChat"];
var kernel = BuildKernel();
Console.WriteLine($"======== Service Ids: {string.Join(", ", serviceIds)} ========");
var function = kernel.CreateFunctionFromPrompt(ChatPrompt, serviceIds.Select(serviceId => new PromptExecutionSettings { ServiceId = serviceId }));
var result = await kernel.InvokeAsync(function);
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to create a KernelFunction from a prompt and specify the model ids of the preferred AI services.
/// When the function is invoked the AI Service will be selected based on the order of the provided model ids.
/// </summary>
[Fact]
public async Task InvokePreconfiguredFunctionWithFirstMatchingModelIdAsync()
{
string[] modelIds = ["gpt-4-1106-preview", TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.OpenAI.ChatModelId];
var kernel = BuildKernel();
Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
var function = kernel.CreateFunctionFromPrompt(ChatPrompt, modelIds.Select((modelId, index) => new PromptExecutionSettings { ServiceId = $"service-{index}", ModelId = modelId }));
var result = await kernel.InvokeAsync(function);
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to invoke a KernelFunction and specify the model id of the AI Service the function will use.
/// </summary>
[Fact]
public async Task InvokePreconfiguredFunctionByModelIdAsync()
{
var modelId = TestConfiguration.OpenAI.ChatModelId;
var kernel = BuildKernel();
Console.WriteLine($"======== Model Id: {modelId} ========");
var function = kernel.CreateFunctionFromPrompt(ChatPrompt);
var result = await kernel.InvokeAsync(function, new(new PromptExecutionSettings { ModelId = modelId }));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows how to invoke a KernelFunction and specify the service id of the AI Service the function will use.
/// </summary>
/// <param name="serviceId">Service Id</param>
[Theory]
[InlineData("AzureOpenAIChat")]
public async Task InvokePreconfiguredFunctionByServiceIdAsync(string serviceId)
{
var kernel = BuildKernel();
Console.WriteLine($"======== Service Id: {serviceId} ========");
var function = kernel.CreateFunctionFromPrompt(ChatPrompt);
var result = await kernel.InvokeAsync(function, new(new PromptExecutionSettings { ServiceId = serviceId }));
Console.WriteLine(result.GetValue<string>());
}
/// <summary>
/// Shows when specifying a non-existent ServiceId the kernel throws an exception.
/// </summary>
/// <param name="serviceId">Service Id</param>
[Theory]
[InlineData("NotFound")]
public async Task InvokePromptByNonExistingServiceIdThrowsExceptionAsync(string serviceId)
{
var kernel = BuildKernel();
Console.WriteLine($"======== Service Id: {serviceId} ========");
await Assert.ThrowsAsync<KernelException>(async () => await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ServiceId = serviceId })));
}
/// <summary>
/// Shows how in the execution settings when no model id is found it falls back to the default service.
/// </summary>
/// <param name="modelId">Model Id</param>
[Theory]
[InlineData("NotFound")]
public async Task InvokePromptByNonExistingModelIdUsesDefaultServiceAsync(string modelId)
{
var kernel = BuildKernel();
Console.WriteLine($"======== Model Id: {modelId} ========");
await kernel.InvokePromptAsync(ChatPrompt, new(new PromptExecutionSettings { ModelId = modelId }));
}
}
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft. All rights reserved.
using Google.Apis.Auth.OAuth2;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using chat completion with Google VertexAI and GoogleAI APIs.
/// </summary>
public sealed class Google_GeminiChatCompletion(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task GoogleAIUsingChatCompletion()
{
Console.WriteLine("============= Google AI - Gemini Chat Completion =============");
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
if (geminiApiKey is null || geminiModelId is null)
{
Console.WriteLine("Gemini credentials not found. Skipping example.");
return;
}
Kernel kernel = Kernel.CreateBuilder()
.AddGoogleAIGeminiChatCompletion(
modelId: geminiModelId,
apiKey: geminiApiKey)
.Build();
await this.ProcessChatAsync(kernel);
}
[Fact]
public async Task VertexAIUsingChatCompletion()
{
Console.WriteLine("============= Vertex AI - Gemini Chat Completion =============");
string? bearerToken = null;
Assert.NotNull(TestConfiguration.VertexAI.ClientId);
Assert.NotNull(TestConfiguration.VertexAI.ClientSecret);
Assert.NotNull(TestConfiguration.VertexAI.Location);
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddVertexAIGeminiChatCompletion(
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
bearerTokenProvider: GetBearerToken,
location: TestConfiguration.VertexAI.Location,
projectId: TestConfiguration.VertexAI.ProjectId)
.Build();
// To generate bearer key, you need installed google sdk or use google web console with command:
//
// gcloud auth print-access-token
//
// Above code pass bearer key as string, it is not recommended way in production code,
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
// You should use bearer key provider, which will be used to generate token on demand:
//
// Example:
//
// Kernel kernel = Kernel.CreateBuilder()
// .AddVertexAIGeminiChatCompletion(
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
// bearerKeyProvider: () =>
// {
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
// // This delegate will be called on every request,
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
// return GetBearerToken();
// },
// location: TestConfiguration.VertexAI.Location,
// projectId: TestConfiguration.VertexAI.ProjectId);
async ValueTask<string> GetBearerToken()
{
if (!string.IsNullOrEmpty(bearerToken))
{
return bearerToken;
}
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = TestConfiguration.VertexAI.ClientId,
ClientSecret = TestConfiguration.VertexAI.ClientSecret
},
["https://www.googleapis.com/auth/cloud-platform"],
"user",
CancellationToken.None);
var userCredential = await credential.WaitAsync(CancellationToken.None);
bearerToken = userCredential.Token.AccessToken;
return bearerToken;
}
await this.ProcessChatAsync(kernel);
}
private async Task ProcessChatAsync(Kernel kernel)
{
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
var chat = kernel.GetRequiredService<IChatCompletionService>();
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for new power tools, any suggestion?");
await MessageOutputAsync(chatHistory);
// First assistant message
var reply = await chat.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
await MessageOutputAsync(chatHistory);
// Second user message
chatHistory.AddUserMessage("I'm looking for a drill, a screwdriver and a hammer.");
await MessageOutputAsync(chatHistory);
// Second assistant message
reply = await chat.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
await MessageOutputAsync(chatHistory);
}
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
return Task.CompletedTask;
}
}
@@ -0,0 +1,164 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Google.Apis.Auth.OAuth2;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
namespace ChatCompletion;
/// <summary>
/// These examples demonstrate different ways of using chat completion with Google VertexAI and GoogleAI APIs.
/// </summary>
public sealed class Google_GeminiChatCompletionStreaming(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task GoogleAIUsingStreamingChatCompletion()
{
Console.WriteLine("============= Google AI - Gemini Chat Completion =============");
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
if (geminiApiKey is null || geminiModelId is null)
{
Console.WriteLine("Gemini credentials not found. Skipping example.");
return;
}
Kernel kernel = Kernel.CreateBuilder()
.AddGoogleAIGeminiChatCompletion(
modelId: geminiModelId,
apiKey: geminiApiKey)
.Build();
await this.ProcessStreamingChatAsync(kernel);
}
[Fact]
public async Task VertexAIUsingStreamingChatCompletion()
{
Console.WriteLine("============= Vertex AI - Gemini Chat Completion =============");
string? bearerToken = null;
Assert.NotNull(TestConfiguration.VertexAI.ClientId);
Assert.NotNull(TestConfiguration.VertexAI.ClientSecret);
Assert.NotNull(TestConfiguration.VertexAI.Location);
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddVertexAIGeminiChatCompletion(
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
bearerTokenProvider: GetBearerToken,
location: TestConfiguration.VertexAI.Location,
projectId: TestConfiguration.VertexAI.ProjectId)
.Build();
// To generate bearer key, you need installed google sdk or use google web console with command:
//
// gcloud auth print-access-token
//
// Above code pass bearer key as string, it is not recommended way in production code,
// especially if IChatCompletionService will be long lived, tokens generated by google sdk lives for 1 hour.
// You should use bearer key provider, which will be used to generate token on demand:
//
// Example:
//
// Kernel kernel = Kernel.CreateBuilder()
// .AddVertexAIGeminiChatCompletion(
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
// bearerKeyProvider: () =>
// {
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
// // This delegate will be called on every request,
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
// return GetBearerKey();
// },
// location: TestConfiguration.VertexAI.Location,
// projectId: TestConfiguration.VertexAI.ProjectId);
async ValueTask<string> GetBearerToken()
{
if (!string.IsNullOrEmpty(bearerToken))
{
return bearerToken;
}
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = TestConfiguration.VertexAI.ClientId,
ClientSecret = TestConfiguration.VertexAI.ClientSecret
},
["https://www.googleapis.com/auth/cloud-platform"],
"user",
CancellationToken.None);
var userCredential = await credential.WaitAsync(CancellationToken.None);
bearerToken = userCredential.Token.AccessToken;
return bearerToken;
}
await this.ProcessStreamingChatAsync(kernel);
}
private async Task ProcessStreamingChatAsync(Kernel kernel)
{
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
var chat = kernel.GetRequiredService<IChatCompletionService>();
// First user message
chatHistory.AddUserMessage("Hi, I'm looking for alternative coffee brew methods, can you help me?");
await MessageOutputAsync(chatHistory);
// First assistant message
var streamingChat = chat.GetStreamingChatMessageContentsAsync(chatHistory);
var reply = await MessageOutputAsync(streamingChat);
chatHistory.Add(reply);
// Second user message
chatHistory.AddUserMessage("Give me the best speciality coffee roasters.");
await MessageOutputAsync(chatHistory);
// Second assistant message
streamingChat = chat.GetStreamingChatMessageContentsAsync(chatHistory);
reply = await MessageOutputAsync(streamingChat);
chatHistory.Add(reply);
}
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
return Task.CompletedTask;
}
private async Task<ChatMessageContent> MessageOutputAsync(IAsyncEnumerable<StreamingChatMessageContent> streamingChat)
{
bool first = true;
StringBuilder messageBuilder = new();
await foreach (var chatMessage in streamingChat)
{
if (first)
{
Console.Write($"{chatMessage.Role}: ");
first = false;
}
Console.Write(chatMessage.Content);
messageBuilder.Append(chatMessage.Content);
}
Console.WriteLine();
Console.WriteLine("------------------------");
return new ChatMessageContent(AuthorRole.Assistant, messageBuilder.ToString());
}
}
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
namespace ChatCompletion;
/// <summary>
/// This sample shows how to use binary file and inline Base64 inputs, like PDFs, with Google Gemini's chat completion.
/// </summary>
public class Google_GeminiChatCompletionWithFile(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task GoogleAIChatCompletionWithLocalFile()
{
Console.WriteLine("============= Google AI - Gemini Chat Completion With Local File =============");
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
.Build();
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
var chatHistory = new ChatHistory("You are a friendly assistant.");
chatHistory.AddUserMessage(
[
new TextContent("What's in this file?"),
new BinaryContent(fileBytes, "application/pdf")
]);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply.Content);
}
[Fact]
public async Task VertexAIChatCompletionWithLocalFile()
{
Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Local File =============");
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
Assert.NotNull(TestConfiguration.VertexAI.Location);
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddVertexAIGeminiChatCompletion(
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
bearerKey: TestConfiguration.VertexAI.BearerKey,
location: TestConfiguration.VertexAI.Location,
projectId: TestConfiguration.VertexAI.ProjectId)
.Build();
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
var chatHistory = new ChatHistory("You are a friendly assistant.");
chatHistory.AddUserMessage(
[
new TextContent("What's in this file?"),
new BinaryContent(fileBytes, "application/pdf"),
]);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply.Content);
}
[Fact]
public async Task GoogleAIChatCompletionWithBase64DataUri()
{
Console.WriteLine("============= Google AI - Gemini Chat Completion With Base64 Data Uri =============");
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
.Build();
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
var dataUri = $"data:application/pdf;base64,{fileBase64}";
var chatHistory = new ChatHistory("You are a friendly assistant.");
chatHistory.AddUserMessage(
[
new TextContent("What's in this file?"),
new BinaryContent(dataUri)
// Google AI Gemini AI does not support arbitrary URIs but we can convert a Base64 URI into InlineData with the correct mimeType.
]);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply.Content);
}
[Fact]
public async Task VertexAIChatCompletionWithBase64DataUri()
{
Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Base64 Data Uri =============");
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
Assert.NotNull(TestConfiguration.VertexAI.Location);
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
Kernel kernel = Kernel.CreateBuilder()
.AddVertexAIGeminiChatCompletion(
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
bearerKey: TestConfiguration.VertexAI.BearerKey,
location: TestConfiguration.VertexAI.Location,
projectId: TestConfiguration.VertexAI.ProjectId)
.Build();
var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
var dataUri = $"data:application/pdf;base64,{fileBase64}";
var chatHistory = new ChatHistory("You are a friendly assistant.");
chatHistory.AddUserMessage(
[
new TextContent("What's in this file?"),
new BinaryContent(dataUri)
// Vertex AI API does not support URIs outside of inline Base64 or GCS buckets within the same project. The bucket that stores the file must be in the same Google Cloud project that's sending the request. You must always provide the mimeType via the metadata property.
// var content = new BinaryContent(gs://generativeai-downloads/files/employees.pdf);
// content.Metadata = new Dictionary<string, object?> { { "mimeType", "application/pdf" } };
]);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply.Content);
}
}

Some files were not shown because too many files have changed in this diff Show More