Merge branch 'microsoft:main' into update-modelid

This commit is contained in:
Sandra Ahlgrimm
2024-01-17 22:20:29 +01:00
committed by GitHub
149 changed files with 5237 additions and 1661 deletions
+54
View File
@@ -0,0 +1,54 @@
param (
[string]$JsonReportPath,
[double]$CoverageThreshold
)
$jsonContent = Get-Content $JsonReportPath -Raw | ConvertFrom-Json
$coverageBelowThreshold = $false
function Get-FormattedValue($number) {
$formattedNumber = "{0:N1}" -f $number
$icon = if ($number -ge $CoverageThreshold) { '✅' } else { '❌' }
return "$formattedNumber% $icon"
}
$lineCoverage = $jsonContent.summary.linecoverage
$branchCoverage = $jsonContent.summary.branchcoverage
if ($lineCoverage -lt $CoverageThreshold -or $branchCoverage -lt $CoverageThreshold) {
$coverageBelowThreshold = $true
}
$totalTableData = [PSCustomObject]@{
'Metric' = 'Total Coverage'
'Line Coverage' = Get-FormattedValue $lineCoverage
'Branch Coverage' = Get-FormattedValue $branchCoverage
}
$totalTableData | Format-Table -AutoSize
$assemblyTableData = @()
foreach ($assembly in $jsonContent.coverage.assemblies) {
$assemblyName = $assembly.name
$assemblyLineCoverage = $assembly.coverage
$assemblyBranchCoverage = $assembly.branchcoverage
if ($assemblyLineCoverage -lt $CoverageThreshold -or $assemblyBranchCoverage -lt $CoverageThreshold) {
$coverageBelowThreshold = $true
}
$assemblyTableData += [PSCustomObject]@{
'Assembly Name' = $assemblyName
'Line' = Get-FormattedValue $assemblyLineCoverage
'Branch' = Get-FormattedValue $assemblyBranchCoverage
}
}
$assemblyTableData | Format-Table -AutoSize
if ($coverageBelowThreshold) {
Write-Host "Code coverage is lower than defined threshold: $CoverageThreshold. Stopping the task."
exit 1
}
+18 -1
View File
@@ -12,6 +12,9 @@ on:
merge_group:
branches: ["main"]
env:
COVERAGE_THRESHOLD: 80
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
@@ -82,7 +85,7 @@ jobs:
run: |
export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | grep -v -E "(Planners.Core.UnitTests.csproj|Experimental.Orchestration.Flow.UnitTests.csproj|Experimental.Assistants.UnitTests.csproj)" | tr '\n' ' ')
for project in $UT_PROJECTS; do
dotnet test -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx
dotnet test -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/"
done
- name: Run Integration Tests
@@ -109,6 +112,20 @@ jobs:
Bing__ApiKey: ${{ secrets.BING__APIKEY }}
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
# Generate test reports and check coverage
- name: Generate test reports
uses: danielpalme/[email protected]
with:
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
targetdir: "./TestResults/Reports"
reporttypes: "JsonSummary"
# Report for production packages only
assemblyfilters: "+Microsoft.SemanticKernel.Abstractions;+Microsoft.SemanticKernel.Core;+Microsoft.SemanticKernel.PromptTemplates.Handlebars;+Microsoft.SemanticKernel.Connectors.OpenAI;+Microsoft.SemanticKernel.Yaml;"
- name: Check coverage
shell: pwsh
run: .github/workflows/check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD
# This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed
dotnet-build-and-test-check:
if: always()
-2
View File
@@ -50,7 +50,6 @@
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageVersion Include="xretry" Version="1.9.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="Polly" Version="8.2.0" />
<!-- Plugins -->
<PackageVersion Include="DocumentFormat.OpenXml" Version="3.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
@@ -66,7 +65,6 @@
<PackageVersion Include="Grpc.Net.Client" Version="2.60.0" />
<PackageVersion Include="protobuf-net" Version="3.2.30" />
<PackageVersion Include="protobuf-net.Reflection" Version="3.2.12" />
<PackageVersion Include="CoreCLR-NCalc" Version="2.2.113" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<!-- Memory stores -->
<PackageVersion Include="Pgvector" Version="0.2.0" />
+6 -8
View File
@@ -118,9 +118,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "System", "System", "{3CDE10
src\InternalUtilities\src\System\EnvExtensions.cs = src\InternalUtilities\src\System\EnvExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.Core", "src\Plugins\Plugins.Core\Plugins.Core.csproj", "{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Type", "Type", "{E85EA4D0-BB7E-4DFD-882F-A76EB8C0B8FF}"
ProjectSection(SolutionItems) = preProject
src\InternalUtilities\src\Type\TypeExtensions.cs = src\InternalUtilities\src\Type\TypeExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCalcPlugins", "samples\NCalcPlugins\NCalcPlugins.csproj", "{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.Core", "src\Plugins\Plugins.Core\Plugins.Core.csproj", "{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelemetryExample", "samples\TelemetryExample\TelemetryExample.csproj", "{C754950A-E16C-4F96-9CC7-9328E361B5AF}"
EndProject
@@ -353,11 +356,6 @@ Global
{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1}.Publish|Any CPU.Build.0 = Publish|Any CPU
{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1}.Release|Any CPU.Build.0 = Release|Any CPU
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA}.Release|Any CPU.Build.0 = Release|Any CPU
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C754950A-E16C-4F96-9CC7-9328E361B5AF}.Publish|Any CPU.ActiveCfg = Release|Any CPU
@@ -500,8 +498,8 @@ Global
{B00AD427-0047-4850-BEF9-BA8237EA9D8B} = {958AD708-F048-4FAF-94ED-D2F2B92748B9}
{1C19D805-3573-4477-BF07-40180FCDE1BD} = {958AD708-F048-4FAF-94ED-D2F2B92748B9}
{3CDE10B2-AE8F-4FC4-8D55-92D4AD32E144} = {958AD708-F048-4FAF-94ED-D2F2B92748B9}
{E85EA4D0-BB7E-4DFD-882F-A76EB8C0B8FF} = {958AD708-F048-4FAF-94ED-D2F2B92748B9}
{0D0C4DAD-E6BC-4504-AE3A-EEA4E35920C1} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132}
{E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{C754950A-E16C-4F96-9CC7-9328E361B5AF} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{E07608CC-D710-4655-BB9E-D22CF3CDD193} = {24503383-A8C4-4255-9998-28D70FE8E99A}
{D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
+60
View File
@@ -0,0 +1,60 @@
# This script is for local use to analyze code coverage in more detail using HTML report.
Param(
[switch]$ProdPackagesOnly = $false
)
# Generate a timestamp for the current date and time
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
# Define paths
$scriptPath = Get-Item -Path $PSScriptRoot
$coverageOutputPath = Join-Path $scriptPath "TestResults\Coverage\$timestamp"
$reportOutputPath = Join-Path $scriptPath "TestResults\Reports\$timestamp"
# Create output directories
New-Item -ItemType Directory -Force -Path $coverageOutputPath
New-Item -ItemType Directory -Force -Path $reportOutputPath
# Find tests for projects ending with 'UnitTests.csproj'
$testProjects = Get-ChildItem $scriptPath -Filter "*UnitTests.csproj" -Recurse
foreach ($project in $testProjects) {
$testProjectPath = $project.FullName
Write-Host "Running tests for project: $($testProjectPath)"
# Run tests
dotnet test $testProjectPath `
--collect:"XPlat Code Coverage" `
--results-directory:$coverageOutputPath `
}
# Install required tools
& dotnet tool install -g coverlet.console
& dotnet tool install -g dotnet-reportgenerator-globaltool
# Generate HTML report
if ($ProdPackagesOnly) {
$assemblies = @(
"+Microsoft.SemanticKernel.Abstractions",
"+Microsoft.SemanticKernel.Core",
"+Microsoft.SemanticKernel.PromptTemplates.Handlebars",
"+Microsoft.SemanticKernel.Connectors.OpenAI",
"+Microsoft.SemanticKernel.Yaml"
)
$assemblyFilters = $assemblies -join ";"
# Generate report for production assemblies only
& reportgenerator -reports:"$coverageOutputPath/**/coverage.cobertura.xml" -targetdir:$reportOutputPath -reporttypes:Html -assemblyfilters:$assemblyFilters
}
else {
& reportgenerator -reports:"$coverageOutputPath/**/coverage.cobertura.xml" -targetdir:$reportOutputPath -reporttypes:Html
}
Write-Host "Code coverage report generated at: $reportOutputPath"
# Open report
$reportIndexHtml = Join-Path $reportOutputPath "index.html"
Invoke-Item -Path $reportIndexHtml
+2 -1
View File
@@ -21,4 +21,5 @@ resharper_condition_is_always_true_or_false_according_to_nullable_api_contract_h
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_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,47 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.Configuration;
using RepoUtils;
using Xunit.Abstractions;
namespace Examples;
public abstract class BaseTest
{
protected ITestOutputHelper Output { get; }
protected BaseTest(ITestOutputHelper output)
{
this.Output = output;
LoadUserSecrets();
}
private static void LoadUserSecrets()
{
IConfigurationRoot configRoot = new ConfigurationBuilder()
.AddJsonFile("appsettings.Development.json", true)
.AddEnvironmentVariables()
.AddUserSecrets<Env>()
.Build();
TestConfiguration.Initialize(configRoot);
}
/// <summary>
/// This method can be substituted by Console.WriteLine when used in Console apps.
/// </summary>
/// <param name="target">Target object to write</param>
protected void WriteLine(object? target = null)
{
this.Output.WriteLine(target ?? string.Empty);
}
/// <summary>
/// Current interface ITestOutputHelper does not have a Write method. This extension method adds it to make it analogous to Console.Write when used in Console apps.
/// </summary>
/// <param name="target">Target object to write</param>
protected void Write(object? target = null)
{
this.Output.WriteLine(target ?? string.Empty);
}
}
@@ -1,14 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Plugins.Core;
using Xunit;
using Xunit.Abstractions;
public static class Example01_MethodFunctions
namespace Examples;
public class Example01_MethodFunctions : BaseTest
{
public static Task RunAsync()
[Fact]
public Task RunAsync()
{
Console.WriteLine("======== Functions ========");
this.WriteLine("======== Functions ========");
// Load native plugin
var text = new TextPlugin();
@@ -16,8 +20,12 @@ public static class Example01_MethodFunctions
// Use function without kernel
var result = text.Uppercase("ciao!");
Console.WriteLine(result);
this.WriteLine(result);
return Task.CompletedTask;
}
public Example01_MethodFunctions(ITestOutputHelper output) : base(output)
{
}
}
@@ -5,13 +5,17 @@ using System.Globalization;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Plugins;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use kernel arguments when invoking functions.
public static class Example03_Arguments
public class Example03_Arguments : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Arguments ========");
this.WriteLine("======== Arguments ========");
Kernel kernel = new();
var textPlugin = kernel.ImportPluginFromType<StaticTextPlugin>();
@@ -26,16 +30,20 @@ public static class Example03_Arguments
// Specify and get the value type as generic parameter
string? resultValue = await kernel.InvokeAsync<string>(textPlugin["AppendDay"], arguments);
Console.WriteLine($"string -> {resultValue}");
this.WriteLine($"string -> {resultValue}");
// If you need to access the result metadata, you can use the non-generic version to get the FunctionResult
FunctionResult functionResult = await kernel.InvokeAsync(textPlugin["AppendDay"], arguments);
var metadata = functionResult.Metadata;
// Specify the type from the FunctionResult
Console.WriteLine($"FunctionResult.GetValue<string>() -> {functionResult.GetValue<string>()}");
this.WriteLine($"FunctionResult.GetValue<string>() -> {functionResult.GetValue<string>()}");
// FunctionResult.ToString() automatically converts the result to string
Console.WriteLine($"FunctionResult.ToString() -> {functionResult}");
this.WriteLine($"FunctionResult.ToString() -> {functionResult}");
}
public Example03_Arguments(ITestOutputHelper output) : base(output)
{
}
}
@@ -4,19 +4,24 @@ using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
public static class Example05_InlineFunctionDefinition
namespace Examples;
public class Example05_InlineFunctionDefinition : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Inline Function Definition ========");
this.WriteLine("======== Inline Function Definition ========");
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (openAIModelId is null || openAIApiKey is null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
this.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
@@ -49,14 +54,18 @@ Event: {{$input}}
var excuseFunction = kernel.CreateFunctionFromPrompt(promptTemplate, new OpenAIPromptExecutionSettings() { MaxTokens = 100, Temperature = 0.4, TopP = 1 });
var result = await kernel.InvokeAsync(excuseFunction, new() { ["input"] = "I missed the F1 final race" });
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
result = await kernel.InvokeAsync(excuseFunction, new() { ["input"] = "sorry I forgot your birthday" });
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
var fixedFunction = kernel.CreateFunctionFromPrompt($"Translate this date {DateTimeOffset.Now:f} to French format", new OpenAIPromptExecutionSettings() { MaxTokens = 100 });
result = await kernel.InvokeAsync(fixedFunction);
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
}
public Example05_InlineFunctionDefinition(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,27 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Plugins.Core;
using Xunit;
using Xunit.Abstractions;
public static class Example06_TemplateLanguage
namespace Examples;
public class Example06_TemplateLanguage : BaseTest
{
/// <summary>
/// Show how to invoke a Method Function written in C#
/// from a Prompt Function written in natural language
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== TemplateLanguage ========");
this.WriteLine("======== TemplateLanguage ========");
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (openAIModelId == null || openAIApiKey == null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
this.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
@@ -46,19 +50,19 @@ Is it weekend time (weekend/not weekend)?
";
// This allows to see the prompt before it's sent to OpenAI
Console.WriteLine("--- Rendered Prompt");
this.WriteLine("--- Rendered Prompt");
var promptTemplateFactory = new KernelPromptTemplateFactory();
var promptTemplate = promptTemplateFactory.Create(new PromptTemplateConfig(FunctionDefinition));
var renderedPrompt = await promptTemplate.RenderAsync(kernel);
Console.WriteLine(renderedPrompt);
this.WriteLine(renderedPrompt);
// Run the prompt / prompt function
var kindOfDay = kernel.CreateFunctionFromPrompt(FunctionDefinition, new OpenAIPromptExecutionSettings() { MaxTokens = 100 });
// Show the result
Console.WriteLine("--- Prompt Function result");
this.WriteLine("--- Prompt Function result");
var result = await kernel.InvokeAsync(kindOfDay);
Console.WriteLine(result.GetValue<string>());
this.WriteLine(result.GetValue<string>());
/* OUTPUT:
@@ -81,4 +85,8 @@ Is it weekend time (weekend/not weekend)?
}
*/
}
public Example06_TemplateLanguage(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,22 +7,27 @@ using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Plugins.Web;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
using Microsoft.SemanticKernel.Plugins.Web.Google;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// The example shows how to use Bing and Google to search for current data
/// you might want to import into your system, e.g. providing AI prompts with
/// recent information, or for AI to generate recent information to display to users.
/// </summary>
public static class Example07_BingAndGooglePlugins
public class Example07_BingAndGooglePlugins : BaseTest
{
public static async Task RunAsync()
[Fact(Skip = "Setup Credentials")]
public async Task RunAsync()
{
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (openAIModelId == null || openAIApiKey == null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
this.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
@@ -36,7 +41,7 @@ public static class Example07_BingAndGooglePlugins
string bingApiKey = TestConfiguration.Bing.ApiKey;
if (bingApiKey == null)
{
Console.WriteLine("Bing credentials not found. Skipping example.");
this.WriteLine("Bing credentials not found. Skipping example.");
}
else
{
@@ -53,7 +58,7 @@ public static class Example07_BingAndGooglePlugins
if (googleApiKey == null || googleSearchEngineId == null)
{
Console.WriteLine("Google credentials not found. Skipping example.");
this.WriteLine("Google credentials not found. Skipping example.");
}
else
{
@@ -62,22 +67,23 @@ public static class Example07_BingAndGooglePlugins
searchEngineId: googleSearchEngineId);
var google = new WebSearchEnginePlugin(googleConnector);
kernel.ImportPluginFromObject(new WebSearchEnginePlugin(googleConnector), "google");
// ReSharper disable once ArrangeThisQualifier
await Example1Async(kernel, "google");
}
}
private static async Task Example1Async(Kernel kernel, string searchPluginName)
private async Task Example1Async(Kernel kernel, string searchPluginName)
{
Console.WriteLine("======== Bing and Google Search Plugins ========");
this.WriteLine("======== Bing and Google Search Plugins ========");
// Run
var question = "What's the largest building in the world?";
var function = kernel.Plugins[searchPluginName]["search"];
var result = await kernel.InvokeAsync(function, new() { ["query"] = question });
Console.WriteLine(question);
Console.WriteLine($"----{searchPluginName}----");
Console.WriteLine(result.GetValue<string>());
this.WriteLine(question);
this.WriteLine($"----{searchPluginName}----");
this.WriteLine(result.GetValue<string>());
/* OUTPUT:
@@ -92,9 +98,9 @@ public static class Example07_BingAndGooglePlugins
*/
}
private static async Task Example2Async(Kernel kernel)
private async Task Example2Async(Kernel kernel)
{
Console.WriteLine("======== Use Search Plugin to answer user questions ========");
this.WriteLine("======== Use Search Plugin to answer user questions ========");
const string SemanticFunction = @"Answer questions only when you know the facts or the information is provided.
When you don't have sufficient information you reply with a list of commands to find the information needed.
@@ -130,7 +136,7 @@ Question: {{ $question }}.
Answer: ";
var question = "Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?";
Console.WriteLine(question);
this.WriteLine(question);
var oracle = kernel.CreateFunctionFromPrompt(SemanticFunction, new OpenAIPromptExecutionSettings() { MaxTokens = 150, Temperature = 0, TopP = 1 });
@@ -148,11 +154,11 @@ Answer: ";
var promptTemplateFactory = new KernelPromptTemplateFactory();
var promptTemplate = promptTemplateFactory.Create(new PromptTemplateConfig(result));
Console.WriteLine("---- Fetching information from Bing...");
this.WriteLine("---- Fetching information from Bing...");
var information = await promptTemplate.RenderAsync(kernel);
Console.WriteLine("Information found:");
Console.WriteLine(information);
this.WriteLine("Information found:");
this.WriteLine(information);
// Run the prompt function again, now including information from Bing
answer = await kernel.InvokeAsync(oracle, new KernelArguments()
@@ -164,11 +170,11 @@ Answer: ";
}
else
{
Console.WriteLine("AI had all the information, no need to query Bing.");
this.WriteLine("AI had all the information, no need to query Bing.");
}
Console.WriteLine("---- ANSWER:");
Console.WriteLine(answer.GetValue<string>());
this.WriteLine("---- ANSWER:");
this.WriteLine(answer.GetValue<string>());
/* OUTPUT:
@@ -188,4 +194,8 @@ Answer: ";
* The exchange rate for EUR to USD is 1.1037097 US Dollars for 1 Euro.
*/
}
public Example07_BingAndGooglePlugins(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,11 +7,16 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use a retry handler within a Semantic Kernel
public static class Example08_RetryHandler
public class Example08_RetryHandler : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a Kernel with the HttpClient
IKernelBuilder builder = Kernel.CreateBuilder();
@@ -29,7 +34,7 @@ public static class Example08_RetryHandler
var logger = kernel.LoggerFactory.CreateLogger(typeof(Example08_RetryHandler));
const string Question = "How popular is the Polly library?";
const string Question = "How do I add a standard resilience handler in IHttpClientBuilder??";
logger.LogInformation("Question: {Question}", Question);
// The call to OpenAI will fail and be retried a few times before eventually failing.
@@ -49,4 +54,8 @@ public static class Example08_RetryHandler
logger.LogInformation("Error: {Message}", ex.Message);
}
}
public Example08_RetryHandler(ITestOutputHelper output) : base(output)
{
}
}
@@ -11,16 +11,22 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Example09_FunctionTypes
namespace Examples;
public class Example09_FunctionTypes : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Method Function types ========");
this.WriteLine("======== Method Function types ========");
var builder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
builder.Services.AddLogging(services => services.AddConsole().SetMinimumLevel(LogLevel.Warning));
builder.Services.AddSingleton(this.Output);
var kernel = builder.Build();
kernel.Culture = new CultureInfo("pt-BR");
@@ -36,7 +42,7 @@ public static class Example09_FunctionTypes
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.InputDateTimeWithStringResult)], new() { ["currentDate"] = DateTime.Now });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.NoInputTaskWithStringResult)]);
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.MultipleInputsWithVoidResult)], new() { ["x"] = "x string", ["y"] = 100, ["z"] = 1.5 });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.ComplexInputWithStringResult)], new() { ["complexObject"] = new LocalExamplePlugin() });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.ComplexInputWithStringResult)], new() { ["complexObject"] = new LocalExamplePlugin(this.Output) });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.InputStringTaskWithStringResult)], new() { ["echoInput"] = "return this" });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.InputStringTaskWithVoidResult)], new() { ["x"] = "x input" });
await kernel.InvokeAsync(plugin[nameof(LocalExamplePlugin.NoInputWithFunctionResult)]);
@@ -60,19 +66,30 @@ public static class Example09_FunctionTypes
// You can also use the kernel.Plugins collection to invoke a function
await kernel.InvokeAsync(kernel.Plugins["Examples"][nameof(LocalExamplePlugin.NoInputWithVoidResult)]);
}
public Example09_FunctionTypes(ITestOutputHelper output) : base(output)
{
}
}
// Task functions when are imported as plugins loose the "Async" suffix if present.
#pragma warning disable IDE1006 // Naming Styles
public class LocalExamplePlugin
{
private readonly ITestOutputHelper _output;
public LocalExamplePlugin(ITestOutputHelper output)
{
this._output = output;
}
/// <summary>
/// Example of using a void function with no input
/// </summary>
[KernelFunction]
public void NoInputWithVoidResult()
{
Console.WriteLine($"Running {nameof(this.NoInputWithVoidResult)} -> No input");
this._output.WriteLine($"Running {nameof(this.NoInputWithVoidResult)} -> No input");
}
/// <summary>
@@ -81,7 +98,7 @@ public class LocalExamplePlugin
[KernelFunction]
public Task NoInputTaskWithVoidResult()
{
Console.WriteLine($"Running {nameof(this.NoInputTaskWithVoidResult)} -> No input");
this._output.WriteLine($"Running {nameof(this.NoInputTaskWithVoidResult)} -> No input");
return Task.CompletedTask;
}
@@ -92,7 +109,7 @@ public class LocalExamplePlugin
public string InputDateTimeWithStringResult(DateTime currentDate)
{
var result = currentDate.ToString(CultureInfo.InvariantCulture);
Console.WriteLine($"Running {nameof(this.InputDateTimeWithStringResult)} -> [currentDate = {currentDate}] -> result: {result}");
this._output.WriteLine($"Running {nameof(this.InputDateTimeWithStringResult)} -> [currentDate = {currentDate}] -> result: {result}");
return result;
}
@@ -103,7 +120,7 @@ public class LocalExamplePlugin
public Task<string> NoInputTaskWithStringResult()
{
var result = "string result";
Console.WriteLine($"Running {nameof(this.NoInputTaskWithStringResult)} -> No input -> result: {result}");
this._output.WriteLine($"Running {nameof(this.NoInputTaskWithStringResult)} -> No input -> result: {result}");
return Task.FromResult(result);
}
@@ -113,7 +130,7 @@ public class LocalExamplePlugin
[KernelFunction]
public void MultipleInputsWithVoidResult(string x, int y, double z)
{
Console.WriteLine($"Running {nameof(this.MultipleInputsWithVoidResult)} -> input: [x = {x}, y = {y}, z = {z}]");
this._output.WriteLine($"Running {nameof(this.MultipleInputsWithVoidResult)} -> input: [x = {x}, y = {y}, z = {z}]");
}
/// <summary>
@@ -123,7 +140,7 @@ public class LocalExamplePlugin
public string ComplexInputWithStringResult(object complexObject)
{
var result = complexObject.GetType().Name;
Console.WriteLine($"Running {nameof(this.ComplexInputWithStringResult)} -> input: [complexObject = {complexObject}] -> result: {result}");
this._output.WriteLine($"Running {nameof(this.ComplexInputWithStringResult)} -> input: [complexObject = {complexObject}] -> result: {result}");
return result;
}
@@ -133,7 +150,7 @@ public class LocalExamplePlugin
[KernelFunction]
public Task<string> InputStringTaskWithStringResult(string echoInput)
{
Console.WriteLine($"Running {nameof(this.InputStringTaskWithStringResult)} -> input: [echoInput = {echoInput}] -> result: {echoInput}");
this._output.WriteLine($"Running {nameof(this.InputStringTaskWithStringResult)} -> input: [echoInput = {echoInput}] -> result: {echoInput}");
return Task.FromResult(echoInput);
}
@@ -143,7 +160,7 @@ public class LocalExamplePlugin
[KernelFunction]
public Task InputStringTaskWithVoidResult(string x)
{
Console.WriteLine($"Running {nameof(this.InputStringTaskWithVoidResult)} -> input: [x = {x}]");
this._output.WriteLine($"Running {nameof(this.InputStringTaskWithVoidResult)} -> input: [x = {x}]");
return Task.CompletedTask;
}
@@ -155,7 +172,7 @@ public class LocalExamplePlugin
{
var myInternalFunction = KernelFunctionFactory.CreateFromMethod(() => { });
var result = new FunctionResult(myInternalFunction);
Console.WriteLine($"Running {nameof(this.NoInputWithFunctionResult)} -> No input -> result: {result.GetType().Name}");
this._output.WriteLine($"Running {nameof(this.NoInputWithFunctionResult)} -> No input -> result: {result.GetType().Name}");
return result;
}
@@ -166,7 +183,7 @@ public class LocalExamplePlugin
public async Task<FunctionResult> NoInputTaskWithFunctionResult(Kernel kernel)
{
var result = await kernel.InvokeAsync(kernel.Plugins["Examples"][nameof(this.NoInputWithVoidResult)]);
Console.WriteLine($"Running {nameof(this.NoInputTaskWithFunctionResult)} -> Injected kernel -> result: {result.GetType().Name}");
this._output.WriteLine($"Running {nameof(this.NoInputTaskWithFunctionResult)} -> Injected kernel -> result: {result.GetType().Name}");
return result;
}
@@ -178,7 +195,7 @@ public class LocalExamplePlugin
public async Task<string> TaskInjectingKernelWithInputTextAndStringResult(Kernel kernel, string textToSummarize)
{
var summary = await kernel.InvokeAsync<string>(kernel.Plugins["SummarizePlugin"]["Summarize"], new() { ["input"] = textToSummarize });
Console.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected kernel + input: [textToSummarize: {textToSummarize[..15]}...{textToSummarize[^15..]}] -> result: {summary}");
this._output.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected kernel + input: [textToSummarize: {textToSummarize[..15]}...{textToSummarize[^15..]}] -> result: {summary}");
return summary!;
}
@@ -189,7 +206,7 @@ public class LocalExamplePlugin
public async Task<string> TaskInjectingKernelFunctionWithStringResult(KernelFunction executingFunction)
{
var result = $"Name: {executingFunction.Name}, Description: {executingFunction.Description}";
Console.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Function -> result: {result}");
this._output.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Function -> result: {result}");
return result;
}
@@ -200,7 +217,7 @@ public class LocalExamplePlugin
public Task TaskInjectingLoggerWithNoResult(ILogger logger)
{
logger.LogWarning("Running {FunctionName} -> Injected Logger", nameof(this.TaskInjectingLoggerWithNoResult));
Console.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Logger");
this._output.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Logger");
return Task.CompletedTask;
}
@@ -214,7 +231,7 @@ public class LocalExamplePlugin
.CreateLogger<LocalExamplePlugin>()
.LogWarning("Running {FunctionName} -> Injected Logger", nameof(this.TaskInjectingLoggerWithNoResult));
Console.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Logger");
this._output.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Logger");
return Task.CompletedTask;
}
@@ -231,7 +248,7 @@ public class LocalExamplePlugin
}
var result = chatMessageContent?.Content;
Console.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Kernel, KernelFunction, KernelArguments, Service Selector -> result: {result}");
this._output.WriteLine($"Running {nameof(this.TaskInjectingKernelWithInputTextAndStringResult)} -> Injected Kernel, KernelFunction, KernelArguments, Service Selector -> result: {result}");
return result ?? string.Empty;
}
@@ -242,7 +259,7 @@ public class LocalExamplePlugin
public async Task<string> TaskInjectingCultureInfoOrIFormatProviderWithStringResult(CultureInfo cultureInfo, IFormatProvider formatProvider)
{
var result = $"Culture Name: {cultureInfo.Name}, FormatProvider Equals CultureInfo?: {formatProvider.Equals(cultureInfo)}";
Console.WriteLine($"Running {nameof(this.TaskInjectingCultureInfoOrIFormatProviderWithStringResult)} -> Injected CultureInfo, IFormatProvider -> result: {result}");
this._output.WriteLine($"Running {nameof(this.TaskInjectingCultureInfoOrIFormatProviderWithStringResult)} -> Injected CultureInfo, IFormatProvider -> result: {result}");
return result;
}
@@ -253,7 +270,7 @@ public class LocalExamplePlugin
public async Task<string> TaskInjectingCancellationTokenWithStringResult(CancellationToken cancellationToken)
{
var result = $"Cancellation resquested: {cancellationToken.IsCancellationRequested}";
Console.WriteLine($"Running {nameof(this.TaskInjectingCultureInfoOrIFormatProviderWithStringResult)} -> Injected Cancellation Token -> result: {result}");
this._output.WriteLine($"Running {nameof(this.TaskInjectingCultureInfoOrIFormatProviderWithStringResult)} -> Injected Cancellation Token -> result: {result}");
return result;
}
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
@@ -8,15 +7,20 @@ using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Plugins.Core;
using Plugins;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Example10_DescribeAllPluginsAndFunctions
namespace Examples;
public class Example10_DescribeAllPluginsAndFunctions : BaseTest
{
/// <summary>
/// Print a list of all the functions imported into the kernel, including function descriptions,
/// list of parameters, parameters descriptions, etc.
/// See the end of the file for a sample of what the output looks like.
/// </summary>
public static Task RunAsync()
[Fact]
public Task RunAsync()
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
@@ -46,10 +50,10 @@ public static class Example10_DescribeAllPluginsAndFunctions
var functions = kernel.Plugins.GetFunctionsMetadata();
Console.WriteLine("**********************************************");
Console.WriteLine("****** Registered plugins and functions ******");
Console.WriteLine("**********************************************");
Console.WriteLine();
WriteLine("**********************************************");
WriteLine("****** Registered plugins and functions ******");
WriteLine("**********************************************");
WriteLine();
foreach (KernelFunctionMetadata func in functions)
{
@@ -59,22 +63,26 @@ public static class Example10_DescribeAllPluginsAndFunctions
return Task.CompletedTask;
}
private static void PrintFunction(KernelFunctionMetadata func)
private void PrintFunction(KernelFunctionMetadata func)
{
Console.WriteLine($"Plugin: {func.PluginName}");
Console.WriteLine($" {func.Name}: {func.Description}");
WriteLine($"Plugin: {func.PluginName}");
WriteLine($" {func.Name}: {func.Description}");
if (func.Parameters.Count > 0)
{
Console.WriteLine(" Params:");
WriteLine(" Params:");
foreach (var p in func.Parameters)
{
Console.WriteLine($" - {p.Name}: {p.Description}");
Console.WriteLine($" default: '{p.DefaultValue}'");
WriteLine($" - {p.Name}: {p.Description}");
WriteLine($" default: '{p.DefaultValue}'");
}
}
Console.WriteLine();
WriteLine();
}
public Example10_DescribeAllPluginsAndFunctions(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,15 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Web;
using Xunit;
using Xunit.Abstractions;
public static class Example11_WebSearchQueries
namespace Examples;
public class Example11_WebSearchQueries : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== WebSearchQueries ========");
WriteLine("======== WebSearchQueries ========");
Kernel kernel = new();
@@ -20,8 +24,8 @@ public static class Example11_WebSearchQueries
var ask = "What's the tallest building in Europe?";
var result = await kernel.InvokeAsync(bing["BingSearchUrl"], new() { ["query"] = ask });
Console.WriteLine(ask + "\n");
Console.WriteLine(result.GetValue<string>());
WriteLine(ask + "\n");
WriteLine(result.GetValue<string>());
/* Expected output:
* ======== WebSearchQueries ========
@@ -31,4 +35,8 @@ public static class Example11_WebSearchQueries
* == DONE ==
*/
}
public Example11_WebSearchQueries(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Core;
using xRetry;
using Xunit.Abstractions;
internal static class Example13_ConversationSummaryPlugin
namespace Examples;
public class Example13_ConversationSummaryPlugin : BaseTest
{
private const string ChatTranscript =
@"
@@ -115,16 +118,17 @@ John: You're welcome. I'm glad we could help. Goodbye!
Jane: Goodbye!
";
public static async Task RunAsync()
[RetryFact(typeof(HttpOperationException))]
public async Task RunAsync()
{
await ConversationSummaryPluginAsync();
await GetConversationActionItemsAsync();
await GetConversationTopicsAsync();
}
private static async Task ConversationSummaryPluginAsync()
private async Task ConversationSummaryPluginAsync()
{
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Summarize ========");
WriteLine("======== SamplePlugins - Conversation Summary Plugin - Summarize ========");
Kernel kernel = InitializeKernel();
KernelPlugin conversationSummaryPlugin = kernel.ImportPluginFromType<ConversationSummaryPlugin>();
@@ -132,13 +136,13 @@ Jane: Goodbye!
FunctionResult summary = await kernel.InvokeAsync(
conversationSummaryPlugin["SummarizeConversation"], new() { ["input"] = ChatTranscript });
Console.WriteLine("Generated Summary:");
Console.WriteLine(summary.GetValue<string>());
WriteLine("Generated Summary:");
WriteLine(summary.GetValue<string>());
}
private static async Task GetConversationActionItemsAsync()
private async Task GetConversationActionItemsAsync()
{
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Action Items ========");
WriteLine("======== SamplePlugins - Conversation Summary Plugin - Action Items ========");
Kernel kernel = InitializeKernel();
KernelPlugin conversationSummary = kernel.ImportPluginFromType<ConversationSummaryPlugin>();
@@ -146,13 +150,13 @@ Jane: Goodbye!
FunctionResult summary = await kernel.InvokeAsync(
conversationSummary["GetConversationActionItems"], new() { ["input"] = ChatTranscript });
Console.WriteLine("Generated Action Items:");
Console.WriteLine(summary.GetValue<string>());
WriteLine("Generated Action Items:");
WriteLine(summary.GetValue<string>());
}
private static async Task GetConversationTopicsAsync()
private async Task GetConversationTopicsAsync()
{
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Topics ========");
WriteLine("======== SamplePlugins - Conversation Summary Plugin - Topics ========");
Kernel kernel = InitializeKernel();
KernelPlugin conversationSummary = kernel.ImportPluginFromType<ConversationSummaryPlugin>();
@@ -160,11 +164,11 @@ Jane: Goodbye!
FunctionResult summary = await kernel.InvokeAsync(
conversationSummary["GetConversationTopics"], new() { ["input"] = ChatTranscript });
Console.WriteLine("Generated Topics:");
Console.WriteLine(summary.GetValue<string>());
WriteLine("Generated Topics:");
WriteLine(summary.GetValue<string>());
}
private static Kernel InitializeKernel()
private Kernel InitializeKernel()
{
Kernel kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
@@ -176,6 +180,10 @@ Jane: Goodbye!
return kernel;
}
public Example13_ConversationSummaryPlugin(ITestOutputHelper output) : base(output)
{
}
}
/* Example Output:
@@ -6,6 +6,10 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Memory;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/* The files contains two examples about SK Semantic Memory.
*
@@ -15,15 +19,16 @@ using Microsoft.SemanticKernel.Memory;
* Semantic Memory allows to store your data like traditional DBs,
* adding the ability to query it using natural language.
*/
public static class Example14_SemanticMemory
public class Example14_SemanticMemory : BaseTest
{
private const string MemoryCollectionName = "SKGitHub";
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("==============================================================");
Console.WriteLine("======== Semantic Memory using Azure AI Search ========");
Console.WriteLine("==============================================================");
WriteLine("==============================================================");
WriteLine("======== Semantic Memory using Azure AI Search ========");
WriteLine("==============================================================");
/* This example leverages Azure AI Search to provide SK with Semantic Memory.
*
@@ -38,9 +43,9 @@ public static class Example14_SemanticMemory
await RunExampleAsync(memoryWithACS);
Console.WriteLine("====================================================");
Console.WriteLine("======== Semantic Memory (volatile, in RAM) ========");
Console.WriteLine("====================================================");
WriteLine("====================================================");
WriteLine("======== Semantic Memory (volatile, in RAM) ========");
WriteLine("====================================================");
/* You can build your own semantic memory combining an Embedding Generator
* with a Memory storage that supports search by similarity (ie semantic search).
@@ -59,7 +64,7 @@ public static class Example14_SemanticMemory
await RunExampleAsync(memoryWithCustomDb);
}
public static async Task RunExampleAsync(ISemanticTextMemory memory)
private async Task RunExampleAsync(ISemanticTextMemory memory)
{
await StoreMemoryAsync(memory);
@@ -98,26 +103,26 @@ public static class Example14_SemanticMemory
*/
}
private static async Task SearchMemoryAsync(ISemanticTextMemory memory, string query)
private async Task SearchMemoryAsync(ISemanticTextMemory memory, string query)
{
Console.WriteLine("\nQuery: " + query + "\n");
WriteLine("\nQuery: " + query + "\n");
var memoryResults = memory.SearchAsync(MemoryCollectionName, query, limit: 2, minRelevanceScore: 0.5);
int i = 0;
await foreach (MemoryQueryResult memoryResult in memoryResults)
{
Console.WriteLine($"Result {++i}:");
Console.WriteLine(" URL: : " + memoryResult.Metadata.Id);
Console.WriteLine(" Title : " + memoryResult.Metadata.Description);
Console.WriteLine(" Relevance: " + memoryResult.Relevance);
Console.WriteLine();
WriteLine($"Result {++i}:");
WriteLine(" URL: : " + memoryResult.Metadata.Id);
WriteLine(" Title : " + memoryResult.Metadata.Description);
WriteLine(" Relevance: " + memoryResult.Relevance);
WriteLine();
}
Console.WriteLine("----------------------");
WriteLine("----------------------");
}
private static async Task StoreMemoryAsync(ISemanticTextMemory memory)
private async Task StoreMemoryAsync(ISemanticTextMemory memory)
{
/* Store some data in the semantic memory.
*
@@ -127,7 +132,7 @@ public static class Example14_SemanticMemory
* care of creating and storing the index
*/
Console.WriteLine("\nAdding some GitHub file URLs and their descriptions to the semantic memory.");
WriteLine("\nAdding some GitHub file URLs and their descriptions to the semantic memory.");
var githubFiles = SampleData();
var i = 0;
foreach (var entry in githubFiles)
@@ -142,7 +147,7 @@ public static class Example14_SemanticMemory
Console.Write($" #{++i} saved.");
}
Console.WriteLine("\n----------------------");
WriteLine("\n----------------------");
}
private static Dictionary<string, string> SampleData()
@@ -161,4 +166,8 @@ public static class Example14_SemanticMemory
= "C# class that defines a volatile embedding store",
};
}
public Example14_SemanticMemory(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
@@ -21,12 +19,19 @@ using Microsoft.SemanticKernel.Plugins.Memory;
using Npgsql;
using RepoUtils;
using StackExchange.Redis;
using Xunit;
using Xunit.Abstractions;
public static class Example15_TextMemoryPlugin
namespace Examples;
public class Example15_TextMemoryPlugin : BaseTest
{
private const string MemoryCollectionName = "aboutMe";
public static async Task RunAsync(CancellationToken cancellationToken = default)
[Theory]
[InlineData("Volatile")]
[InlineData("AzureAISearch")]
public async Task RunAsync(string provider)
{
IMemoryStore store;
@@ -35,7 +40,11 @@ public static class Example15_TextMemoryPlugin
///////////////////////////////////////////////////////////////////////////////////////////
// Volatile Memory Store - an in-memory store that is not persisted
store = new VolatileMemoryStore();
switch (provider)
{
case "AzureAISearch": store = CreateSampleAzureAISearchMemoryStore(); break;
default: store = new VolatileMemoryStore(); break;
}
// Sqlite Memory Store - a file-based store that persists data in a Sqlite database
// store = await CreateSampleSqliteMemoryStoreAsync();
@@ -70,58 +79,58 @@ public static class Example15_TextMemoryPlugin
// Kusto Memory Store
// store = CreateSampleKustoMemoryStore();
await RunWithStoreAsync(store, cancellationToken);
await RunWithStoreAsync(store);
}
private static async Task<IMemoryStore> CreateSampleSqliteMemoryStoreAsync()
private async Task<IMemoryStore> CreateSampleSqliteMemoryStoreAsync()
{
IMemoryStore store = await SqliteMemoryStore.ConnectAsync("memories.sqlite");
return store;
}
private static async Task<IMemoryStore> CreateSampleDuckDbMemoryStoreAsync()
private async Task<IMemoryStore> CreateSampleDuckDbMemoryStoreAsync()
{
IMemoryStore store = await DuckDBMemoryStore.ConnectAsync("memories.duckdb");
return store;
}
private static IMemoryStore CreateSampleMongoDBMemoryStore()
private IMemoryStore CreateSampleMongoDBMemoryStore()
{
IMemoryStore store = new MongoDBMemoryStore(TestConfiguration.MongoDB.ConnectionString, "memoryPluginExample");
return store;
}
private static IMemoryStore CreateSampleAzureAISearchMemoryStore()
private IMemoryStore CreateSampleAzureAISearchMemoryStore()
{
IMemoryStore store = new AzureAISearchMemoryStore(TestConfiguration.AzureAISearch.Endpoint, TestConfiguration.AzureAISearch.ApiKey);
return store;
}
private static IMemoryStore CreateSampleChromaMemoryStore()
private IMemoryStore CreateSampleChromaMemoryStore()
{
IMemoryStore store = new ChromaMemoryStore(TestConfiguration.Chroma.Endpoint, ConsoleLogger.LoggerFactory);
return store;
}
private static IMemoryStore CreateSampleQdrantMemoryStore()
private IMemoryStore CreateSampleQdrantMemoryStore()
{
IMemoryStore store = new QdrantMemoryStore(TestConfiguration.Qdrant.Endpoint, 1536, ConsoleLogger.LoggerFactory);
return store;
}
private static IMemoryStore CreateSamplePineconeMemoryStore()
private IMemoryStore CreateSamplePineconeMemoryStore()
{
IMemoryStore store = new PineconeMemoryStore(TestConfiguration.Pinecone.Environment, TestConfiguration.Pinecone.ApiKey, ConsoleLogger.LoggerFactory);
return store;
}
private static IMemoryStore CreateSampleWeaviateMemoryStore()
private IMemoryStore CreateSampleWeaviateMemoryStore()
{
IMemoryStore store = new WeaviateMemoryStore(TestConfiguration.Weaviate.Endpoint, TestConfiguration.Weaviate.ApiKey);
return store;
}
private static async Task<IMemoryStore> CreateSampleRedisMemoryStoreAsync()
private async Task<IMemoryStore> CreateSampleRedisMemoryStoreAsync()
{
string configuration = TestConfiguration.Redis.Configuration;
ConnectionMultiplexer connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configuration);
@@ -146,7 +155,7 @@ public static class Example15_TextMemoryPlugin
return store;
}
private static async Task RunWithStoreAsync(IMemoryStore memoryStore, CancellationToken cancellationToken)
private async Task RunWithStoreAsync(IMemoryStore memoryStore)
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
@@ -165,25 +174,25 @@ public static class Example15_TextMemoryPlugin
//
// This is a simple way to store memories from a code perspective, without using the Kernel.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Console.WriteLine("== PART 1a: Saving Memories through the ISemanticTextMemory object ==");
WriteLine("== PART 1a: Saving Memories through the ISemanticTextMemory object ==");
Console.WriteLine("Saving memory with key 'info1': \"My name is Andrea\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea", cancellationToken: cancellationToken);
WriteLine("Saving memory with key 'info1': \"My name is Andrea\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
Console.WriteLine("Saving memory with key 'info2': \"I work as a tourist operator\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I work as a tourist operator", cancellationToken: cancellationToken);
WriteLine("Saving memory with key 'info2': \"I work as a tourist operator\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I work as a tourist operator");
Console.WriteLine("Saving memory with key 'info3': \"I've been living in Seattle since 2005\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I've been living in Seattle since 2005", cancellationToken: cancellationToken);
WriteLine("Saving memory with key 'info3': \"I've been living in Seattle since 2005\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I've been living in Seattle since 2005");
Console.WriteLine("Saving memory with key 'info4': \"I visited France and Italy five times since 2015\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015", cancellationToken: cancellationToken);
WriteLine("Saving memory with key 'info4': \"I visited France and Italy five times since 2015\"");
await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
// Retrieve a memory
Console.WriteLine("== PART 1b: Retrieving Memories through the ISemanticTextMemory object ==");
MemoryQueryResult? lookup = await textMemory.GetAsync(MemoryCollectionName, "info1", cancellationToken: cancellationToken);
Console.WriteLine("Memory with key 'info1':" + lookup?.Metadata.Text ?? "ERROR: memory not found");
Console.WriteLine();
WriteLine("== PART 1b: Retrieving Memories through the ISemanticTextMemory object ==");
MemoryQueryResult? lookup = await textMemory.GetAsync(MemoryCollectionName, "info1");
WriteLine("Memory with key 'info1':" + lookup?.Metadata.Text ?? "ERROR: memory not found");
WriteLine();
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 2: Create TextMemoryPlugin, store and retrieve memories through the Kernel.
@@ -191,30 +200,30 @@ public static class Example15_TextMemoryPlugin
// This enables prompt functions and the AI (via Planners) to access memories
/////////////////////////////////////////////////////////////////////////////////////////////////////
Console.WriteLine("== PART 2a: Saving Memories through the Kernel with TextMemoryPlugin and the 'Save' function ==");
WriteLine("== PART 2a: Saving Memories through the Kernel with TextMemoryPlugin and the 'Save' function ==");
// Import the TextMemoryPlugin into the Kernel for other functions
var memoryPlugin = kernel.ImportPluginFromObject(new TextMemoryPlugin(textMemory));
// Save a memory with the Kernel
Console.WriteLine("Saving memory with key 'info5': \"My family is from New York\"");
WriteLine("Saving memory with key 'info5': \"My family is from New York\"");
await kernel.InvokeAsync(memoryPlugin["Save"], new()
{
[TextMemoryPlugin.InputParam] = "My family is from New York",
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.KeyParam] = "info5",
}, cancellationToken);
});
// Retrieve a specific memory with the Kernel
Console.WriteLine("== PART 2b: Retrieving Memories through the Kernel with TextMemoryPlugin and the 'Retrieve' function ==");
WriteLine("== PART 2b: Retrieving Memories through the Kernel with TextMemoryPlugin and the 'Retrieve' function ==");
var result = await kernel.InvokeAsync(memoryPlugin["Retrieve"], new KernelArguments()
{
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.KeyParam] = "info5"
}, cancellationToken);
});
Console.WriteLine("Memory with key 'info5':" + result.GetValue<string>() ?? "ERROR: memory not found");
Console.WriteLine();
WriteLine("Memory with key 'info5':" + result.GetValue<string>() ?? "ERROR: memory not found");
WriteLine();
/////////////////////////////////////////////////////////////////////////////////////////////////////
// PART 3: Recall similar ideas with semantic search
@@ -222,24 +231,23 @@ public static class Example15_TextMemoryPlugin
// Uses AI Embeddings for fuzzy lookup of memories based on intent, rather than a specific key.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Console.WriteLine("== PART 3: Recall (similarity search) with AI Embeddings ==");
WriteLine("== PART 3: Recall (similarity search) with AI Embeddings ==");
Console.WriteLine("== PART 3a: Recall (similarity search) with ISemanticTextMemory ==");
Console.WriteLine("Ask: where did I grow up?");
WriteLine("== PART 3a: Recall (similarity search) with ISemanticTextMemory ==");
WriteLine("Ask: where did I grow up?");
await foreach (var answer in textMemory.SearchAsync(
collection: MemoryCollectionName,
query: "where did I grow up?",
limit: 2,
minRelevanceScore: 0.79,
withEmbeddings: true,
cancellationToken: cancellationToken))
withEmbeddings: true))
{
Console.WriteLine($"Answer: {answer.Metadata.Text}");
WriteLine($"Answer: {answer.Metadata.Text}");
}
Console.WriteLine("== PART 3b: Recall (similarity search) with Kernel and TextMemoryPlugin 'Recall' function ==");
Console.WriteLine("Ask: where do I live?");
WriteLine("== PART 3b: Recall (similarity search) with Kernel and TextMemoryPlugin 'Recall' function ==");
WriteLine("Ask: where do I live?");
result = await kernel.InvokeAsync(memoryPlugin["Recall"], new()
{
@@ -247,10 +255,10 @@ public static class Example15_TextMemoryPlugin
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.LimitParam] = "2",
[TextMemoryPlugin.RelevanceParam] = "0.79",
}, cancellationToken);
});
Console.WriteLine($"Answer: {result.GetValue<string>()}");
Console.WriteLine();
WriteLine($"Answer: {result.GetValue<string>()}");
WriteLine();
/*
Output:
@@ -271,7 +279,7 @@ public static class Example15_TextMemoryPlugin
// the text generation model to answer a natural language query.
/////////////////////////////////////////////////////////////////////////////////////////////////////
Console.WriteLine("== PART 4: Using TextMemoryPlugin 'Recall' function in a Prompt Function ==");
WriteLine("== PART 4: Using TextMemoryPlugin 'Recall' function in a Prompt Function ==");
// Build a prompt function that uses memory to find facts
const string RecallFunctionDefinition = @"
@@ -295,10 +303,10 @@ Answer:
[TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
[TextMemoryPlugin.LimitParam] = "2",
[TextMemoryPlugin.RelevanceParam] = "0.79",
}, cancellationToken);
});
Console.WriteLine("Ask: Do I live in the same town where I grew up?");
Console.WriteLine($"Answer: {result.GetValue<string>()}");
WriteLine("Ask: Do I live in the same town where I grew up?");
WriteLine($"Answer: {result.GetValue<string>()}");
/*
Approximate Output:
@@ -310,25 +318,29 @@ Answer:
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
Console.WriteLine("== PART 5: Cleanup, deleting database collection ==");
WriteLine("== PART 5: Cleanup, deleting database collection ==");
Console.WriteLine("Printing Collections in DB...");
var collections = memoryStore.GetCollectionsAsync(cancellationToken);
WriteLine("Printing Collections in DB...");
var collections = memoryStore.GetCollectionsAsync();
await foreach (var collection in collections)
{
Console.WriteLine(collection);
WriteLine(collection);
}
Console.WriteLine();
WriteLine();
Console.WriteLine("Removing Collection {0}", MemoryCollectionName);
await memoryStore.DeleteCollectionAsync(MemoryCollectionName, cancellationToken);
Console.WriteLine();
WriteLine($"Removing Collection {MemoryCollectionName}");
await memoryStore.DeleteCollectionAsync(MemoryCollectionName);
WriteLine();
Console.WriteLine($"Printing Collections in DB (after removing {MemoryCollectionName})...");
collections = memoryStore.GetCollectionsAsync(cancellationToken);
WriteLine($"Printing Collections in DB (after removing {MemoryCollectionName})...");
collections = memoryStore.GetCollectionsAsync();
await foreach (var collection in collections)
{
Console.WriteLine(collection);
WriteLine(collection);
}
}
public Example15_TextMemoryPlugin(ITestOutputHelper output) : base(output)
{
}
}
@@ -8,6 +8,10 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.TextGeneration;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/**
* The following example shows how to plug a custom text generation model into SK.
@@ -24,18 +28,12 @@ using Microsoft.SemanticKernel.TextGeneration;
*
* Refer to example 33 for streaming chat completion.
*/
public static class Example16_CustomLLM
public class Example16_CustomLLM : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task CustomTextGenerationWithKernelFunctionAsync()
{
await CustomTextGenerationWithKernelFunctionAsync();
await CustomTextGenerationAsync();
await CustomTextGenerationStreamAsync();
}
private static async Task CustomTextGenerationWithKernelFunctionAsync()
{
Console.WriteLine("\n======== Custom LLM - Text Completion - KernelFunction ========");
WriteLine("\n======== Custom LLM - Text Completion - KernelFunction ========");
IKernelBuilder builder = Kernel.CreateBuilder();
// Add your text generation service as a singleton instance
@@ -48,39 +46,41 @@ public static class Example16_CustomLLM
var paragraphWritingFunction = kernel.CreateFunctionFromPrompt(FunctionDefinition);
const string Input = "Why AI is awesome";
Console.WriteLine($"Function input: {Input}\n");
WriteLine($"Function input: {Input}\n");
var result = await paragraphWritingFunction.InvokeAsync(kernel, new() { ["input"] = Input });
Console.WriteLine(result);
WriteLine(result);
}
private static async Task CustomTextGenerationAsync()
[Fact]
public async Task CustomTextGenerationAsync()
{
Console.WriteLine("\n======== Custom LLM - Text Completion - Raw ========");
WriteLine("\n======== Custom LLM - Text Completion - Raw ========");
const string Prompt = "Write one paragraph on why AI is awesome.";
var completionService = new MyTextGenerationService();
Console.WriteLine($"Prompt: {Prompt}\n");
WriteLine($"Prompt: {Prompt}\n");
var result = await completionService.GetTextContentAsync(Prompt);
Console.WriteLine(result);
WriteLine(result);
}
private static async Task CustomTextGenerationStreamAsync()
[Fact]
public async Task CustomTextGenerationStreamAsync()
{
Console.WriteLine("\n======== Custom LLM - Text Completion - Raw Streaming ========");
WriteLine("\n======== Custom LLM - Text Completion - Raw Streaming ========");
const string Prompt = "Write one paragraph on why AI is awesome.";
var completionService = new MyTextGenerationService();
Console.WriteLine($"Prompt: {Prompt}\n");
WriteLine($"Prompt: {Prompt}\n");
await foreach (var message in completionService.GetStreamingTextContentsAsync(Prompt))
{
Console.Write(message);
Write(message);
}
Console.WriteLine();
WriteLine();
}
/// <summary>
@@ -117,4 +117,8 @@ providing personalized recommendations, entertainment, and assistance. AI is awe
});
}
}
public Example16_CustomLLM(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,18 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with OpenAI ChatGPT API
public static class Example17_ChatGPT
public class Example17_ChatGPT : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task OpenAIChatSampleAsync()
{
await AzureOpenAIChatSampleAsync();
await OpenAIChatSampleAsync();
WriteLine("======== Open AI - ChatGPT ========");
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
await StartChatAsync(chatCompletionService);
/* Output:
@@ -43,18 +50,10 @@ public static class Example17_ChatGPT
*/
}
private static async Task OpenAIChatSampleAsync()
[Fact]
public async Task AzureOpenAIChatSampleAsync()
{
Console.WriteLine("======== Open AI - ChatGPT ========");
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
await StartChatAsync(chatCompletionService);
}
private static async Task AzureOpenAIChatSampleAsync()
{
Console.WriteLine("======== Azure Open AI - ChatGPT ========");
WriteLine("======== Azure Open AI - ChatGPT ========");
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
@@ -65,10 +64,10 @@ public static class Example17_ChatGPT
await StartChatAsync(chatCompletionService);
}
private static async Task StartChatAsync(IChatCompletionService chatGPT)
private async Task StartChatAsync(IChatCompletionService chatGPT)
{
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
WriteLine("Chat content:");
WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
@@ -94,13 +93,17 @@ public static class Example17_ChatGPT
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private static Task MessageOutputAsync(ChatHistory chatHistory)
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
WriteLine($"{message.Role}: {message.Content}");
WriteLine("------------------------");
return Task.CompletedTask;
}
public Example17_ChatGPT(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,19 +7,18 @@ using Microsoft.Extensions.Http.Resilience;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.TextToImage;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with OpenAI Dall-E 2 to create images
public static class Example18_DallE
public class Example18_DallE : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task OpenAIDallEAsync()
{
await OpenAIDallEAsync();
await AzureOpenAIDallEAsync();
}
private static async Task OpenAIDallEAsync()
{
Console.WriteLine("======== OpenAI Dall-E 2 Text To Image ========");
WriteLine("======== OpenAI Dall-E 2 Text To Image ========");
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAITextToImage(TestConfiguration.OpenAI.ApiKey) // Add your text to image service
@@ -31,8 +30,8 @@ public static class Example18_DallE
var imageDescription = "A cute baby sea otter";
var image = await dallE.GenerateImageAsync(imageDescription, 256, 256);
Console.WriteLine(imageDescription);
Console.WriteLine("Image URL: " + image);
WriteLine(imageDescription);
WriteLine("Image URL: " + image);
/* Output:
@@ -41,7 +40,7 @@ public static class Example18_DallE
*/
Console.WriteLine("======== Chat with images ========");
WriteLine("======== Chat with images ========");
var chatGPT = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory(
@@ -52,23 +51,23 @@ public static class Example18_DallE
var msg = "Hi, I'm from Tokyo, where are you from?";
chatHistory.AddUserMessage(msg);
Console.WriteLine("User: " + msg);
WriteLine("User: " + msg);
var reply = await chatGPT.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
image = await dallE.GenerateImageAsync(reply.Content!, 256, 256);
Console.WriteLine("Bot: " + image);
Console.WriteLine("Img description: " + reply);
WriteLine("Bot: " + image);
WriteLine("Img description: " + reply);
msg = "Oh, wow. Not sure where that is, could you provide more details?";
chatHistory.AddUserMessage(msg);
Console.WriteLine("User: " + msg);
WriteLine("User: " + msg);
reply = await chatGPT.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
image = await dallE.GenerateImageAsync(reply.Content!, 256, 256);
Console.WriteLine("Bot: " + image);
Console.WriteLine("Img description: " + reply);
WriteLine("Bot: " + image);
WriteLine("Img description: " + reply);
/* Output:
@@ -83,9 +82,10 @@ public static class Example18_DallE
*/
}
public static async Task AzureOpenAIDallEAsync()
[Fact(Skip = "Generating the Image can take too long and often break the test")]
public async Task AzureOpenAIDallEAsync()
{
Console.WriteLine("========Azure OpenAI Dall-E 3 Text To Image ========");
WriteLine("========Azure OpenAI Dall-E 3 Text To Image ========");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAITextToImage( // Add your text to image service
@@ -105,6 +105,7 @@ public static class Example18_DallE
c.AddStandardResilienceHandler().Configure(o =>
{
o.Retry.MaxRetryAttempts = 5;
o.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
});
});
@@ -114,8 +115,8 @@ public static class Example18_DallE
var imageDescription = "A cute baby sea otter";
var image = await dallE.GenerateImageAsync(imageDescription, 1024, 1024);
Console.WriteLine(imageDescription);
Console.WriteLine("Image URL: " + image);
WriteLine(imageDescription);
WriteLine("Image URL: " + image);
/* Output:
@@ -124,7 +125,7 @@ public static class Example18_DallE
*/
Console.WriteLine("======== Chat with images ========");
WriteLine("======== Chat with images ========");
var chatGPT = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory(
@@ -135,23 +136,23 @@ public static class Example18_DallE
var msg = "Hi, I'm from Tokyo, where are you from?";
chatHistory.AddUserMessage(msg);
Console.WriteLine("User: " + msg);
WriteLine("User: " + msg);
var reply = await chatGPT.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
image = await dallE.GenerateImageAsync(reply.Content!, 1024, 1024);
Console.WriteLine("Bot: " + image);
Console.WriteLine("Img description: " + reply);
WriteLine("Bot: " + image);
WriteLine("Img description: " + reply);
msg = "Oh, wow. Not sure where that is, could you provide more details?";
chatHistory.AddUserMessage(msg);
Console.WriteLine("User: " + msg);
WriteLine("User: " + msg);
reply = await chatGPT.GetChatMessageContentAsync(chatHistory);
chatHistory.Add(reply);
image = await dallE.GenerateImageAsync(reply.Content!, 1024, 1024);
Console.WriteLine("Bot: " + image);
Console.WriteLine("Img description: " + reply);
WriteLine("Bot: " + image);
WriteLine("Img description: " + reply);
/* Output:
@@ -165,4 +166,8 @@ public static class Example18_DallE
*/
}
public Example18_DallE(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,25 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with HuggingFace API.
public static class Example20_HuggingFace
public class Example20_HuggingFace : BaseTest
{
public static async Task RunAsync()
{
await RunInferenceApiExampleAsync();
await RunLlamaExampleAsync();
}
/// <summary>
/// This example uses HuggingFace Inference API to access hosted models.
/// More information here: <see href="https://huggingface.co/inference-api"/>
/// </summary>
private static async Task RunInferenceApiExampleAsync()
[Fact]
public async Task RunInferenceApiExampleAsync()
{
Console.WriteLine("\n======== HuggingFace Inference API example ========\n");
WriteLine("\n======== HuggingFace Inference API example ========\n");
Kernel kernel = Kernel.CreateBuilder()
.AddHuggingFaceTextGeneration(
@@ -31,7 +29,7 @@ public static class Example20_HuggingFace
var result = await kernel.InvokeAsync(questionAnswerFunction, new() { ["input"] = "What is New York?" });
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
/// <summary>
@@ -45,20 +43,21 @@ public static class Example20_HuggingFace
/// Note: Your Hugging Face account email address MUST match the email you provide on the Meta website, or your request will not be approved.
/// </remarks>
/// </summary>
private static async Task RunLlamaExampleAsync()
[Fact(Skip = "Requires local model or Huggingface Pro subscription")]
public async Task RunLlamaExampleAsync()
{
Console.WriteLine("\n======== HuggingFace Llama 2 example ========\n");
WriteLine("\n======== HuggingFace Llama 2 example ========\n");
// HuggingFace Llama 2 model: https://huggingface.co/meta-llama/Llama-2-7b-hf
const string Model = "meta-llama/Llama-2-7b-hf";
// HuggingFace local HTTP server endpoint
const string Endpoint = "http://localhost:5000/completions";
// const string Endpoint = "http://localhost:5000/completions";
Kernel kernel = Kernel.CreateBuilder()
.AddHuggingFaceTextGeneration(
model: Model,
endpoint: Endpoint,
//endpoint: Endpoint,
apiKey: TestConfiguration.HuggingFace.ApiKey)
.Build();
@@ -66,6 +65,10 @@ public static class Example20_HuggingFace
var result = await kernel.InvokeAsync(questionAnswerFunction, new() { ["input"] = "What is New York?" });
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
public Example20_HuggingFace(ITestOutputHelper output) : base(output)
{
}
}
@@ -5,22 +5,18 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using Xunit;
using Xunit.Abstractions;
public static class Example21_OpenAIPlugins
namespace Examples;
public class Example21_OpenAIPlugins : BaseTest
{
public static Task RunAsync()
{
// Uncomment after filling the template below
// await RunOpenAIPluginAsync();
// --------------- Example using Klarna's OpenAI plugin ------------------------
return CallKlarnaAsync();
}
/// <summary>
/// Generic template on how to call OpenAI plugins
/// </summary>
public static async Task RunOpenAIPluginAsync()
[Fact(Skip = "Run it only after filling the template below")]
public async Task RunOpenAIPluginAsync()
{
Kernel kernel = new();
@@ -38,10 +34,11 @@ public static class Example21_OpenAIPlugins
var result = functionResult.GetValue<RestApiOperationResponse>();
Console.WriteLine("Function execution result: {0}", result?.Content?.ToString());
WriteLine($"Function execution result: {result?.Content}");
}
public static async Task CallKlarnaAsync()
[Fact]
public async Task CallKlarnaAsync()
{
Kernel kernel = new();
@@ -58,6 +55,10 @@ public static class Example21_OpenAIPlugins
var result = functionResult.GetValue<RestApiOperationResponse>();
Console.WriteLine("Function execution result: {0}", result?.Content?.ToString());
WriteLine($"Function execution result: {result?.Content}");
}
public Example21_OpenAIPlugins(ITestOutputHelper output) : base(output)
{
}
}
@@ -13,8 +13,12 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using Resources;
using Xunit;
using Xunit.Abstractions;
public static class Example22_OpenAIPlugin_AzureKeyVault
namespace Examples;
public class Example22_OpenAIPlugin_AzureKeyVault : BaseTest
{
private const string SecretName = "Foo";
private const string SecretValue = "Bar";
@@ -38,7 +42,8 @@ public static class Example22_OpenAIPlugin_AzureKeyVault
///
/// 5. Replace your tenant ID with the "TENANT_ID" placeholder in dotnet/samples/KernelSyntaxExamples/Resources/22-ai-plugin.json
/// </summary>
public static async Task RunAsync()
[Fact(Skip = "Setup credentials")]
public async Task RunAsync()
{
var authenticationProvider = new OpenAIAuthenticationProvider(
new Dictionary<string, Dictionary<string, string>>()
@@ -78,7 +83,7 @@ public static class Example22_OpenAIPlugin_AzureKeyVault
await GetSecretFromAzureKeyVaultWithRetryAsync(kernel, plugin);
}
public static async Task AddSecretToAzureKeyVaultAsync(Kernel kernel, KernelPlugin plugin)
private async Task AddSecretToAzureKeyVaultAsync(Kernel kernel, KernelPlugin plugin)
{
// Add arguments for required parameters, arguments for optional ones can be skipped.
var arguments = new KernelArguments
@@ -97,7 +102,7 @@ public static class Example22_OpenAIPlugin_AzureKeyVault
Console.WriteLine("SetSecret function result: {0}", result?.Content?.ToString());
}
public static async Task GetSecretFromAzureKeyVaultWithRetryAsync(Kernel kernel, KernelPlugin plugin)
private static async Task GetSecretFromAzureKeyVaultWithRetryAsync(Kernel kernel, KernelPlugin plugin)
{
// Add arguments for required parameters, arguments for optional ones can be skipped.
var arguments = new KernelArguments();
@@ -111,6 +116,10 @@ public static class Example22_OpenAIPlugin_AzureKeyVault
Console.WriteLine("GetSecret function result: {0}", result?.Content?.ToString());
}
public Example22_OpenAIPlugin_AzureKeyVault(ITestOutputHelper output) : base(output)
{
}
}
#region Utility Classes
@@ -12,8 +12,12 @@ using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using Xunit;
using Xunit.Abstractions;
public static class Example24_OpenApiPlugin_Jira
namespace Examples;
public class Example24_OpenApiPlugin_Jira : BaseTest
{
private static readonly JsonSerializerOptions s_jsonOptionsCache = new()
{
@@ -37,7 +41,8 @@ public static class Example24_OpenApiPlugin_Jira
/// instance then select "Manage account".
/// 4. Configure the secrets as described by the ReadMe.md in the dotnet/samples/KernelSyntaxExamples folder.
/// </summary>
public static async Task RunAsync()
[Fact(Skip = "Setup credentials")]
public async Task RunAsync()
{
Kernel kernel = new();
@@ -90,10 +95,10 @@ public static class Example24_OpenApiPlugin_Jira
// Run operation via the semantic kernel
var result = await kernel.InvokeAsync(jiraFunctions["GetIssue"], arguments);
Console.WriteLine("\n\n\n");
WriteLine("\n\n\n");
var formattedContent = JsonSerializer.Serialize(
result.GetValue<RestApiOperationResponse>(), s_jsonOptionsCache);
Console.WriteLine("GetIssue jiraPlugin response: \n{0}", formattedContent);
WriteLine($"GetIssue jiraPlugin response: \n{formattedContent}");
// AddComment Function
arguments["issueKey"] = "TEST-2";
@@ -102,10 +107,10 @@ public static class Example24_OpenApiPlugin_Jira
// Run operation via the semantic kernel
result = await kernel.InvokeAsync(jiraFunctions["AddComment"], arguments);
Console.WriteLine("\n\n\n");
WriteLine("\n\n\n");
formattedContent = JsonSerializer.Serialize(result.GetValue<RestApiOperationResponse>(), s_jsonOptionsCache);
Console.WriteLine("AddComment jiraPlugin response: \n{0}", formattedContent);
WriteLine($"AddComment jiraPlugin response: \n{formattedContent}");
}
#region Example of authentication providers
@@ -251,4 +256,8 @@ public static class Example24_OpenApiPlugin_Jira
}
#endregion
public Example24_OpenApiPlugin_Jira(ITestOutputHelper output) : base(output)
{
}
}
@@ -10,6 +10,10 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Memory;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// This sample provides a custom implementation of <see cref="IMemoryStore"/> that is read only.
@@ -18,26 +22,27 @@ using Microsoft.SemanticKernel.Memory;
/// of <see cref="IMemoryStore"/> has a single collection, and thus does not need to be named.
/// It also assumes that the JSON formatted data can be deserialized into <see cref="MemoryRecord"/> objects.
/// </summary>
public static class Example25_ReadOnlyMemoryStore
public class Example25_ReadOnlyMemoryStore : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
var store = new ReadOnlyMemoryStore(s_jsonVectorEntries);
var embedding = new ReadOnlyMemory<float>(new float[] { 22, 4, 6 });
Console.WriteLine("Reading data from custom read-only memory store");
WriteLine("Reading data from custom read-only memory store");
var memoryRecord = await store.GetAsync("collection", "key3");
if (memoryRecord != null)
{
Console.WriteLine("ID = {0}, Embedding = {1}", memoryRecord.Metadata.Id, string.Join(", ", MemoryMarshal.ToEnumerable(memoryRecord.Embedding)));
WriteLine($"ID = {memoryRecord.Metadata.Id}, Embedding = {string.Join(", ", MemoryMarshal.ToEnumerable(memoryRecord.Embedding))}");
}
Console.WriteLine("Getting most similar vector to {0}", string.Join(", ", MemoryMarshal.ToEnumerable(embedding)));
WriteLine($"Getting most similar vector to {string.Join(", ", MemoryMarshal.ToEnumerable(embedding))}");
var result = await store.GetNearestMatchAsync("collection", embedding, 0.0);
if (result.HasValue)
{
Console.WriteLine("Embedding = {0}, Similarity = {1}", string.Join(", ", MemoryMarshal.ToEnumerable(result.Value.Item1.Embedding)), result.Value.Item2);
WriteLine($"ID = {string.Join(", ", MemoryMarshal.ToEnumerable(result.Value.Item1.Embedding))}, Embedding = {result.Value.Item2}");
}
}
@@ -236,4 +241,8 @@ public static class Example25_ReadOnlyMemoryStore
""timestamp"": null
}
]";
public Example25_ReadOnlyMemoryStore(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,10 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// This example shows how to connect your app to Azure OpenAI using
@@ -18,11 +21,12 @@ using Microsoft.SemanticKernel.ChatCompletion;
/// -Shared tokens
/// -etc.
/// </summary>
public static class Example26_AADAuth
public class Example26_AADAuth : BaseTest
{
public static async Task RunAsync()
[Fact(Skip = "Setup credentials")]
public async Task RunAsync()
{
Console.WriteLine("======== SK with AAD Auth ========");
WriteLine("======== SK with AAD Auth ========");
// Optional: choose which authentication to support
var authOptions = new DefaultAzureCredentialOptions
@@ -55,8 +59,12 @@ public static class Example26_AADAuth
// Bot reply
var reply = await chatGPT.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply);
WriteLine(reply);
/* Output: Why did the hourglass go to the doctor? Because it was feeling a little run down! */
}
public Example26_AADAuth(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,17 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// This example shows how to use GPT3.5 Chat model for prompts and prompt functions.
/// </summary>
public static class Example27_PromptFunctionsUsingChatGPT
public class Example27_PromptFunctionsUsingChatGPT : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Using Chat GPT model for text generation ========");
WriteLine("======== Using Chat GPT model for text generation ========");
Kernel kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
@@ -25,7 +29,7 @@ public static class Example27_PromptFunctionsUsingChatGPT
"List the two planets closest to '{{$input}}', excluding moons, using bullet points.");
var result = await func.InvokeAsync(kernel, new() { ["input"] = "Jupiter" });
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
/*
Output:
@@ -33,4 +37,8 @@ public static class Example27_PromptFunctionsUsingChatGPT
- Uranus
*/
}
public Example27_PromptFunctionsUsingChatGPT(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,6 +7,10 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Plugins.Core;
using Resources;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// Scenario:
@@ -31,11 +35,12 @@ using Resources;
/// Out of scope and not in the example: if needed, one could go further and use a semantic
/// function (with extra cost) asking AI to generate the text to send to the Chat model.
/// </summary>
public static class Example30_ChatWithPrompts
public class Example30_ChatWithPrompts : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Chat with prompts ========");
WriteLine("======== Chat with prompts ========");
/* Load 3 files:
* - 30-system-prompt.txt: the system prompt, used to initialize the chat session.
@@ -76,12 +81,12 @@ public static class Example30_ChatWithPrompts
// Render the system prompt. This string is used to configure the chat.
// This contains the context, ie a piece of a wikipedia page selected by the user.
string systemMessage = await promptTemplateFactory.Create(new PromptTemplateConfig(systemPromptTemplate)).RenderAsync(kernel, arguments);
Console.WriteLine($"------------------------------------\n{systemMessage}");
WriteLine($"------------------------------------\n{systemMessage}");
// Render the user prompt. This string is the query sent by the user
// This contains the user request, ie "extract locations as a bullet point list"
string userMessage = await promptTemplateFactory.Create(new PromptTemplateConfig(userPromptTemplate)).RenderAsync(kernel, arguments);
Console.WriteLine($"------------------------------------\n{userMessage}");
WriteLine($"------------------------------------\n{userMessage}");
// Client used to request answers
var chatCompletion = kernel.GetRequiredService<IChatCompletionService>();
@@ -96,7 +101,7 @@ public static class Example30_ChatWithPrompts
// Finally, get the response from AI
var answer = await chatCompletion.GetChatMessageContentAsync(chatHistory);
Console.WriteLine($"------------------------------------\n{answer}");
WriteLine($"------------------------------------\n{answer}");
/*
@@ -121,4 +126,8 @@ public static class Example30_ChatWithPrompts
*/
}
public Example30_ChatWithPrompts(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,9 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.TextGeneration;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/**
* The following example shows how to use Semantic Kernel with streaming text completion.
@@ -15,17 +18,12 @@ using Microsoft.SemanticKernel.TextGeneration;
*
* Refer to example 33 for streaming chat completion.
*/
public static class Example32_StreamingCompletion
public class Example32_StreamingCompletion : BaseTest
{
public static async Task RunAsync()
[Fact]
public Task AzureOpenAITextGenerationStreamAsync()
{
await AzureOpenAITextGenerationStreamAsync();
await OpenAITextGenerationStreamAsync();
}
private static async Task AzureOpenAITextGenerationStreamAsync()
{
Console.WriteLine("======== Azure OpenAI - Text Completion - Raw Streaming ========");
WriteLine("======== Azure OpenAI - Text Completion - Raw Streaming ========");
var textGeneration = new AzureOpenAITextGenerationService(
deploymentName: TestConfiguration.AzureOpenAI.DeploymentName,
@@ -33,19 +31,20 @@ public static class Example32_StreamingCompletion
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ModelId);
await TextGenerationStreamAsync(textGeneration);
return this.TextGenerationStreamAsync(textGeneration);
}
private static async Task OpenAITextGenerationStreamAsync()
[Fact]
public Task OpenAITextGenerationStreamAsync()
{
Console.WriteLine("======== Open AI - Text Completion - Raw Streaming ========");
WriteLine("======== Open AI - Text Completion - Raw Streaming ========");
var textGeneration = new OpenAITextGenerationService("text-davinci-003", TestConfiguration.OpenAI.ApiKey);
var textGeneration = new OpenAITextGenerationService("gpt-3.5-turbo-instruct", TestConfiguration.OpenAI.ApiKey);
await TextGenerationStreamAsync(textGeneration);
return this.TextGenerationStreamAsync(textGeneration);
}
private static async Task TextGenerationStreamAsync(ITextGenerationService textGeneration)
private async Task TextGenerationStreamAsync(ITextGenerationService textGeneration)
{
var executionSettings = new OpenAIPromptExecutionSettings()
{
@@ -58,12 +57,16 @@ public static class Example32_StreamingCompletion
var prompt = "Write one paragraph why AI is awesome";
Console.WriteLine("Prompt: " + prompt);
WriteLine("Prompt: " + prompt);
await foreach (var content in textGeneration.GetStreamingTextContentsAsync(prompt, executionSettings))
{
Console.Write(content);
Write(content);
}
Console.WriteLine();
WriteLine();
}
public Example32_StreamingCompletion(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,32 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with streaming Chat Completion
public static class Example33_StreamingChat
public class Example33_StreamingChat : BaseTest
{
public static async Task RunAsync()
[Fact]
public Task OpenAIChatStreamSampleAsync()
{
await AzureOpenAIChatStreamSampleAsync();
await OpenAIChatStreamSampleAsync();
}
private static async Task OpenAIChatStreamSampleAsync()
{
Console.WriteLine("======== Open AI - ChatGPT Streaming ========");
WriteLine("======== Open AI - ChatGPT Streaming ========");
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
await StartStreamingChatAsync(chatCompletionService);
return this.StartStreamingChatAsync(chatCompletionService);
}
private static async Task AzureOpenAIChatStreamSampleAsync()
[Fact]
public Task AzureOpenAIChatStreamSampleAsync()
{
Console.WriteLine("======== Azure Open AI - ChatGPT Streaming ========");
WriteLine("======== Azure Open AI - ChatGPT Streaming ========");
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
@@ -34,13 +33,13 @@ public static class Example33_StreamingChat
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
await StartStreamingChatAsync(chatCompletionService);
return this.StartStreamingChatAsync(chatCompletionService);
}
private static async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
private async Task StartStreamingChatAsync(IChatCompletionService chatCompletionService)
{
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
WriteLine("Chat content:");
WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian, expert about books");
await MessageOutputAsync(chatHistory);
@@ -60,7 +59,7 @@ public static class Example33_StreamingChat
await StreamMessageOutputAsync(chatCompletionService, chatHistory, AuthorRole.Assistant);
}
private static async Task StreamMessageOutputAsync(IChatCompletionService chatCompletionService, ChatHistory chatHistory, AuthorRole authorRole)
private async Task StreamMessageOutputAsync(IChatCompletionService chatCompletionService, ChatHistory chatHistory, AuthorRole authorRole)
{
bool roleWritten = false;
string fullMessage = string.Empty;
@@ -69,31 +68,35 @@ public static class Example33_StreamingChat
{
if (!roleWritten && chatUpdate.Role.HasValue)
{
Console.Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
Write($"{chatUpdate.Role.Value}: {chatUpdate.Content}");
roleWritten = true;
}
if (chatUpdate.Content is { Length: > 0 })
{
fullMessage += chatUpdate.Content;
Console.Write(chatUpdate.Content);
Write(chatUpdate.Content);
}
}
Console.WriteLine("\n------------------------");
WriteLine("\n------------------------");
chatHistory.AddMessage(authorRole, fullMessage);
}
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private static Task MessageOutputAsync(ChatHistory chatHistory)
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
WriteLine($"{message.Role}: {message.Content}");
WriteLine("------------------------");
return Task.CompletedTask;
}
public Example33_StreamingChat(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,14 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Grpc;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use gRPC plugins.
public static class Example35_GrpcPlugins
public class Example35_GrpcPlugins : BaseTest
{
public static async Task RunAsync()
[Fact(Skip = "Setup crendentials")]
public async Task RunAsync()
{
Kernel kernel = new();
@@ -25,6 +29,10 @@ public static class Example35_GrpcPlugins
// Run
var result = await kernel.InvokeAsync(plugin["<operation-name>"], arguments);
Console.WriteLine("Plugin response: {0}", result.GetValue<string>());
WriteLine($"Plugin response: {result.GetValue<string>()}");
}
public Example35_GrpcPlugins(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,22 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with streaming Multiple Results Chat Completion.
public static class Example36_MultiCompletion
public class Example36_MultiCompletion : BaseTest
{
public static async Task RunAsync()
[Fact]
public Task AzureOpenAIMultiChatCompletionAsync()
{
await AzureOpenAIMultiChatCompletionAsync();
await OpenAIMultiChatCompletionAsync();
}
private static async Task AzureOpenAIMultiChatCompletionAsync()
{
Console.WriteLine("======== Azure OpenAI - Multiple Chat Completion ========");
WriteLine("======== Azure OpenAI - Multiple Chat Completion ========");
var chatCompletionService = new AzureOpenAIChatCompletionService(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
@@ -24,21 +22,22 @@ public static class Example36_MultiCompletion
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
await ChatCompletionAsync(chatCompletionService);
return ChatCompletionAsync(chatCompletionService);
}
private static async Task OpenAIMultiChatCompletionAsync()
[Fact]
public Task OpenAIMultiChatCompletionAsync()
{
Console.WriteLine("======== Open AI - Multiple Chat Completion ========");
WriteLine("======== Open AI - Multiple Chat Completion ========");
var chatCompletionService = new OpenAIChatCompletionService(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey);
await ChatCompletionAsync(chatCompletionService);
return ChatCompletionAsync(chatCompletionService);
}
private static async Task ChatCompletionAsync(IChatCompletionService chatCompletionService)
private async Task ChatCompletionAsync(IChatCompletionService chatCompletionService)
{
var executionSettings = new OpenAIPromptExecutionSettings()
{
@@ -55,10 +54,14 @@ public static class Example36_MultiCompletion
foreach (var chatMessageChoice in await chatCompletionService.GetChatMessageContentsAsync(chatHistory, executionSettings))
{
Console.Write(chatMessageChoice.Content);
Console.WriteLine("\n-------------\n");
Write(chatMessageChoice.Content ?? string.Empty);
WriteLine("\n-------------\n");
}
Console.WriteLine();
WriteLine();
}
public Example36_MultiCompletion(ITestOutputHelper output) : base(output)
{
}
}
@@ -6,14 +6,19 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following examples show how to use SK SDK in applications using DI/IoC containers.
public static class Example40_DIContainer
public class Example40_DIContainer : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
var collection = new ServiceCollection();
collection.AddSingleton<ILoggerFactory>(ConsoleLogger.LoggerFactory);
collection.AddSingleton(ConsoleLogger.LoggerFactory);
collection.AddOpenAITextGeneration(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey);
collection.AddSingleton<Kernel>();
@@ -56,4 +61,8 @@ public static class Example40_DIContainer
this._logger.LogWarning("Result - {0}", result.GetValue<string>());
}
}
public Example40_DIContainer(ITestOutputHelper output) : base(output)
{
}
}
@@ -2,32 +2,21 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// These examples show how to use HttpClient and HttpClientFactory within SK SDK.
public static class Example41_HttpClientUsage
public class Example41_HttpClientUsage : BaseTest
{
public static Task RunAsync()
{
//Examples showing how to use HttpClient.
UseDefaultHttpClient();
UseCustomHttpClient();
//Examples showing how to use HttpClientFactory.
UseBasicRegistrationWithHttpClientFactory();
UseNamedRegistrationWitHttpClientFactory();
return Task.CompletedTask;
}
/// <summary>
/// Demonstrates the usage of the default HttpClient provided by the SK SDK.
/// </summary>
private static void UseDefaultHttpClient()
[Fact]
public void UseDefaultHttpClient()
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
@@ -39,7 +28,8 @@ public static class Example41_HttpClientUsage
/// <summary>
/// Demonstrates the usage of a custom HttpClient.
/// </summary>
private static void UseCustomHttpClient()
[Fact]
public void UseCustomHttpClient()
{
using var httpClient = new HttpClient();
@@ -55,7 +45,8 @@ public static class Example41_HttpClientUsage
/// <summary>
/// Demonstrates the "basic usage" approach for HttpClientFactory.
/// </summary>
private static void UseBasicRegistrationWithHttpClientFactory()
[Fact]
public void UseBasicRegistrationWithHttpClientFactory()
{
//More details - https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#basic-usage
var serviceCollection = new ServiceCollection();
@@ -77,7 +68,8 @@ public static class Example41_HttpClientUsage
/// <summary>
/// Demonstrates the "named clients" approach for HttpClientFactory.
/// </summary>
private static void UseNamedRegistrationWitHttpClientFactory()
[Fact]
public void UseNamedRegistrationWitHttpClientFactory()
{
// More details https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory#named-clients
@@ -102,4 +94,8 @@ public static class Example41_HttpClientUsage
.Build();
});
}
public Example41_HttpClientUsage(ITestOutputHelper output) : base(output)
{
}
}
@@ -5,51 +5,62 @@
// You can access the builder using Kernel.CreateBuilder().
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Core;
using Xunit;
using Xunit.Abstractions;
public static class Example42_KernelBuilder
namespace Examples;
public class Example42_KernelBuilder : BaseTest
{
public static Task RunAsync()
[Fact]
public void BuildKernelWithAzureChatCompletion()
{
string azureOpenAIKey = TestConfiguration.AzureOpenAI.ApiKey;
string azureOpenAIEndpoint = TestConfiguration.AzureOpenAI.Endpoint;
string azureOpenAIChatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string azureOpenAIChatModelId = TestConfiguration.AzureOpenAI.ChatModelId;
// KernelBuilder provides a simple way to configure a Kernel. This constructs a kernel
// with logging and an Azure OpenAI chat completion service configured.
Kernel kernel1 = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: azureOpenAIChatDeploymentName,
endpoint: azureOpenAIEndpoint,
apiKey: azureOpenAIKey,
modelId: azureOpenAIChatModelId)
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
}
[Fact]
public void BuildKernelUsingServiceCollection()
{
// For greater flexibility and to incorporate arbitrary services, KernelBuilder.Services
// provides direct access to an underlying IServiceCollection.
IKernelBuilder builder = Kernel.CreateBuilder();
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information))
.AddHttpClient()
.AddAzureOpenAIChatCompletion(
deploymentName: azureOpenAIChatDeploymentName,
endpoint: azureOpenAIEndpoint,
apiKey: azureOpenAIKey,
modelId: azureOpenAIChatModelId);
.AddHttpClient()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
Kernel kernel2 = builder.Build();
}
[Fact]
public void BuildKernelWithPlugins()
{
// Plugins may also be configured via the corresponding Plugins property.
builder = Kernel.CreateBuilder();
var builder = Kernel.CreateBuilder();
builder.Plugins.AddFromType<HttpPlugin>();
Kernel kernel3 = builder.Build();
}
[Fact]
public void BuildKernelUsingServiceProvider()
{
// Every call to KernelBuilder.Build creates a new Kernel instance, with a new service provider
// and a new plugin collection.
builder = Kernel.CreateBuilder();
var builder = Kernel.CreateBuilder();
Debug.Assert(!ReferenceEquals(builder.Build(), builder.Build()));
// KernelBuilder provides a convenient API for creating Kernel instances. However, it is just a
@@ -59,32 +70,38 @@ public static class Example42_KernelBuilder
services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
services.AddHttpClient();
services.AddAzureOpenAIChatCompletion(
deploymentName: azureOpenAIChatDeploymentName,
endpoint: azureOpenAIEndpoint,
apiKey: azureOpenAIKey,
modelId: azureOpenAIChatModelId);
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
Kernel kernel4 = new(services.BuildServiceProvider());
// Kernels can also be constructed and resolved via such a dependency injection container.
services.AddTransient<Kernel>();
Kernel kernel5 = services.BuildServiceProvider().GetRequiredService<Kernel>();
}
[Fact]
public void BuildKernelUsingServiceCollectionExtension()
{
// In fact, the AddKernel method exists to simplify this, registering a singleton KernelPluginCollection
// that can be populated automatically with all IKernelPlugins registered in the collection, and a
// transient Kernel that can then automatically be constructed from the service provider and resulting
// plugins collection.
services = new ServiceCollection();
var services = new ServiceCollection();
services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
services.AddHttpClient();
services.AddKernel().AddAzureOpenAIChatCompletion(
deploymentName: azureOpenAIChatDeploymentName,
endpoint: azureOpenAIEndpoint,
apiKey: azureOpenAIKey,
modelId: azureOpenAIChatModelId);
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
services.AddSingleton<KernelPlugin>(sp => KernelPluginFactory.CreateFromType<TimePlugin>(serviceProvider: sp));
services.AddSingleton<KernelPlugin>(sp => KernelPluginFactory.CreateFromType<HttpPlugin>(serviceProvider: sp));
Kernel kernel6 = services.BuildServiceProvider().GetRequiredService<Kernel>();
}
return Task.CompletedTask;
public Example42_KernelBuilder(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,15 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Example43_GetModelResult
namespace Examples;
public class Example43_GetModelResult : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task GetTokenUsageMetadataAsync()
{
Console.WriteLine("======== Inline Function Definition + Invocation ========");
WriteLine("======== Inline Function Definition + Invocation ========");
// Create kernel
Kernel kernel = Kernel.CreateBuilder()
@@ -26,8 +30,12 @@ public static class Example43_GetModelResult
FunctionResult result = await kernel.InvokeAsync(myFunction, new() { ["input"] = "travel" });
// Display results
Console.WriteLine(result.GetValue<string>());
Console.WriteLine(result.Metadata?["Usage"]?.AsJson());
Console.WriteLine();
WriteLine(result.GetValue<string>());
WriteLine(result.Metadata?["Usage"]?.AsJson());
WriteLine();
}
public Example43_GetModelResult(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,23 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with Multiple Results Text Completion as streaming
public static class Example44_MultiChatCompletion
public class Example44_MultiChatCompletion : BaseTest
{
public static async Task RunAsync()
[Fact]
public Task AzureOpenAIMultiChatCompletionAsync()
{
await AzureOpenAIMultiChatCompletionAsync();
await OpenAIMultiChatCompletionAsync();
}
private static async Task AzureOpenAIMultiChatCompletionAsync()
{
Console.WriteLine("======== Azure OpenAI - Multiple Chat Completion ========");
WriteLine("======== Azure OpenAI - Multiple Chat Completion ========");
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
@@ -25,19 +23,20 @@ public static class Example44_MultiChatCompletion
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
await RunChatAsync(chatCompletionService);
return RunChatAsync(chatCompletionService);
}
private static async Task OpenAIMultiChatCompletionAsync()
[Fact]
public Task OpenAIMultiChatCompletionAsync()
{
Console.WriteLine("======== Open AI - Multiple Chat Completion ========");
WriteLine("======== Open AI - Multiple Chat Completion ========");
OpenAIChatCompletionService chatCompletionService = new(modelId: TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
await RunChatAsync(chatCompletionService);
return RunChatAsync(chatCompletionService);
}
private static async Task RunChatAsync(IChatCompletionService chatCompletionService)
private async Task RunChatAsync(IChatCompletionService chatCompletionService)
{
var chatHistory = new ChatHistory("You are a librarian, expert about books");
@@ -61,19 +60,23 @@ public static class Example44_MultiChatCompletion
await MessageOutputAsync(chatHistory);
}
Console.WriteLine();
WriteLine();
}
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private static Task MessageOutputAsync(ChatHistory chatHistory)
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
WriteLine($"{message.Role}: {message.Content}");
WriteLine("------------------------");
return Task.CompletedTask;
}
public Example44_MultiChatCompletion(ITestOutputHelper output) : base(output)
{
}
}
@@ -6,21 +6,18 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// The following example shows how to use Semantic Kernel with multiple streaming chat completion results.
public static class Example45_MultiStreamingChatCompletion
public class Example45_MultiStreamingChatCompletion : BaseTest
{
// First procress multiple Azure OpenAI chat completions
// and then multiple plain OpenAI chat completions.
public static async Task RunAsync()
[Fact]
public Task AzureOpenAIMultiStreamingChatCompletionAsync()
{
await AzureOpenAIMultiStreamingChatCompletionAsync();
await OpenAIMultiStreamingChatCompletionAsync();
}
private static async Task AzureOpenAIMultiStreamingChatCompletionAsync()
{
Console.WriteLine("======== Azure OpenAI - Multiple Chat Completions - Raw Streaming ========");
WriteLine("======== Azure OpenAI - Multiple Chat Completions - Raw Streaming ========");
AzureOpenAIChatCompletionService chatCompletionService = new(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
@@ -28,18 +25,19 @@ public static class Example45_MultiStreamingChatCompletion
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId);
await StreamingChatCompletionAsync(chatCompletionService, 3);
return StreamingChatCompletionAsync(chatCompletionService, 3);
}
private static async Task OpenAIMultiStreamingChatCompletionAsync()
[Fact]
public Task OpenAIMultiStreamingChatCompletionAsync()
{
Console.WriteLine("======== OpenAI - Multiple Chat Completions - Raw Streaming ========");
WriteLine("======== OpenAI - Multiple Chat Completions - Raw Streaming ========");
OpenAIChatCompletionService chatCompletionService = new(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
await StreamingChatCompletionAsync(chatCompletionService, 3);
return StreamingChatCompletionAsync(chatCompletionService, 3);
}
/// <summary>
@@ -47,7 +45,7 @@ public static class Example45_MultiStreamingChatCompletion
/// </summary>
/// <param name="chatCompletionService">Chat completion service to use</param>
/// <param name="numResultsPerPrompt">Number of results to get for each chat completion request</param>
private static async Task StreamingChatCompletionAsync(IChatCompletionService chatCompletionService,
private async Task StreamingChatCompletionAsync(IChatCompletionService chatCompletionService,
int numResultsPerPrompt)
{
var executionSettings = new OpenAIPromptExecutionSettings()
@@ -62,24 +60,25 @@ public static class Example45_MultiStreamingChatCompletion
var consoleLinesPerResult = 10;
ClearDisplayByAddingEmptyLines();
// Uncomment this if you want to use a console app to display the results
// ClearDisplayByAddingEmptyLines();
var prompt = "Hi, I'm looking for 5 random title names for sci-fi books";
await ProcessStreamAsyncEnumerableAsync(chatCompletionService, prompt, executionSettings, consoleLinesPerResult);
Console.WriteLine();
WriteLine();
// Set cursor position to after displayed results
Console.SetCursorPosition(0, executionSettings.ResultsPerPrompt * consoleLinesPerResult);
// Console.SetCursorPosition(0, executionSettings.ResultsPerPrompt * consoleLinesPerResult);
Console.WriteLine();
WriteLine();
}
/// <summary>
/// Does the actual streaming and display of the chat completion.
/// </summary>
private static async Task ProcessStreamAsyncEnumerableAsync(IChatCompletionService chatCompletionService, string prompt,
private async Task ProcessStreamAsyncEnumerableAsync(IChatCompletionService chatCompletionService, string prompt,
OpenAIPromptExecutionSettings executionSettings, int consoleLinesPerResult)
{
var messagesPerChoice = new Dictionary<int, string>();
@@ -90,12 +89,13 @@ public static class Example45_MultiStreamingChatCompletion
{
// Set cursor position to the beginning of where this choice (i.e. this result of
// a single multi-result request) is to be displayed.
Console.SetCursorPosition(0, chatUpdate.ChoiceIndex * consoleLinesPerResult + 1);
// Console.SetCursorPosition(0, chatUpdate.ChoiceIndex * consoleLinesPerResult + 1);
// The first time around, start choice text with role information
if (!messagesPerChoice.ContainsKey(chatUpdate.ChoiceIndex))
{
messagesPerChoice[chatUpdate.ChoiceIndex] = $"Role: {chatUpdate.Role ?? new AuthorRole()}\n";
Write($"Choice index: {chatUpdate.ChoiceIndex}, Role: {chatUpdate.Role ?? new AuthorRole()}");
}
// Add latest completion bit, if any
@@ -105,18 +105,30 @@ public static class Example45_MultiStreamingChatCompletion
}
// Overwrite what is currently in the console area for the updated choice
Console.Write(messagesPerChoice[chatUpdate.ChoiceIndex]);
// Console.Write(messagesPerChoice[chatUpdate.ChoiceIndex]);
Write($"Choice index: {chatUpdate.ChoiceIndex}, Content: {chatUpdate.Content}");
}
// Display the aggregated results
foreach (string message in messagesPerChoice.Values)
{
WriteLine("-------------------");
WriteLine(message);
}
}
/// <summary>
/// Add enough new lines to clear the console window.
/// </summary>
private static void ClearDisplayByAddingEmptyLines()
private void ClearDisplayByAddingEmptyLines()
{
for (int i = 0; i < Console.WindowHeight - 2; i++)
{
Console.WriteLine();
WriteLine();
}
}
public Example45_MultiStreamingChatCompletion(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,15 +1,118 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Planning.Handlebars;
using Microsoft.SemanticKernel.Plugins.Core;
using RepoUtils;
using xRetry;
using Xunit;
using Xunit.Abstractions;
internal static class Example48_GroundednessChecks
namespace Examples;
public class Example48_GroundednessChecks : BaseTest
{
[RetryFact(typeof(HttpOperationException))]
public async Task GroundednessCheckingAsync()
{
WriteLine("\n======== Groundedness Checks ========");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
string folder = RepoFiles.SamplePluginsPath();
var summarizePlugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "SummarizePlugin"));
var groundingPlugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "GroundingPlugin"));
var create_summary = summarizePlugin["Summarize"];
var entityExtraction = groundingPlugin["ExtractEntities"];
var reference_check = groundingPlugin["ReferenceCheckEntities"];
var entity_excision = groundingPlugin["ExciseEntities"];
var summaryText = @"
My father, a respected resident of Milan, was a close friend of a merchant named Beaufort who, after a series of
misfortunes, moved to Zurich in poverty. My father was upset by his friend's troubles and sought him out,
finding him in a mean street. Beaufort had saved a small sum of money, but it was not enough to support him and
his daughter, Mary. Mary procured work to eek out a living, but after ten months her father died, leaving
her a beggar. My father came to her aid and two years later they married.
";
KernelArguments variables = new()
{
["input"] = summaryText,
["topic"] = "people and places",
["example_entities"] = "John, Jane, mother, brother, Paris, Rome"
};
var extractionResult = (await kernel.InvokeAsync(entityExtraction, variables)).ToString();
WriteLine("======== Extract Entities ========");
WriteLine(extractionResult);
variables["input"] = extractionResult;
variables["reference_context"] = GroundingText;
var groundingResult = (await kernel.InvokeAsync(reference_check, variables)).ToString();
WriteLine("\n======== Reference Check ========");
WriteLine(groundingResult);
variables["input"] = summaryText;
variables["ungrounded_entities"] = groundingResult;
var excisionResult = await kernel.InvokeAsync(entity_excision, variables);
WriteLine("\n======== Excise Entities ========");
WriteLine(excisionResult.GetValue<string>());
}
[Fact(Skip = "Unreliable - Needs attention")]
public async Task PlanningWithGroundednessAsync()
{
var targetTopic = "people and places";
var samples = "John, Jane, mother, brother, Paris, Rome";
var ask = @$"Make a summary of the following text. Then make a list of entities
related to {targetTopic} (such as {samples}) which are present in the summary.
Take this list of entities, and from it make another list of those which are not
grounded in the original input text. Finally, rewrite your summary to remove the entities
which are not grounded in the original.
Text:\n{GroundingText};
";
WriteLine("\n======== Planning - Groundedness Checks ========");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
string folder = RepoFiles.SamplePluginsPath();
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "SummarizePlugin"));
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "GroundingPlugin"));
kernel.ImportPluginFromType<TextPlugin>();
var planner = new HandlebarsPlanner();
var plan = await planner.CreatePlanAsync(kernel, ask);
WriteLine($"======== Goal: ========\n{ask}");
WriteLine($"======== Plan ========\n{plan}");
var result = await plan.InvokeAsync(kernel);
WriteLine("======== Result ========");
WriteLine(result);
}
private const string GroundingText = @"""I am by birth a Genevese, and my family is one of the most distinguished of that republic.
My ancestors had been for many years counsellors and syndics, and my father had filled several public situations
with honour and reputation.He was respected by all who knew him for his integrity and indefatigable attention
@@ -47,107 +150,8 @@ the chamber. He came like a protecting spirit to the poor girl, who committed he
interment of his friend he conducted her to Geneva and placed her under the protection of a relation.Two years
after this event Caroline became his wife.""";
public static async Task RunAsync()
public Example48_GroundednessChecks(ITestOutputHelper output) : base(output)
{
await GroundednessCheckingAsync();
await PlanningWithGroundednessAsync();
}
public static async Task GroundednessCheckingAsync()
{
Console.WriteLine("\n======== Groundedness Checks ========");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
string folder = RepoFiles.SamplePluginsPath();
var summarizePlugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "SummarizePlugin"));
var groundingPlugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "GroundingPlugin"));
var create_summary = summarizePlugin["Summarize"];
var entityExtraction = groundingPlugin["ExtractEntities"];
var reference_check = groundingPlugin["ReferenceCheckEntities"];
var entity_excision = groundingPlugin["ExciseEntities"];
var summaryText = @"
My father, a respected resident of Milan, was a close friend of a merchant named Beaufort who, after a series of
misfortunes, moved to Zurich in poverty. My father was upset by his friend's troubles and sought him out,
finding him in a mean street. Beaufort had saved a small sum of money, but it was not enough to support him and
his daughter, Mary. Mary procured work to eek out a living, but after ten months her father died, leaving
her a beggar. My father came to her aid and two years later they married.
";
KernelArguments variables = new()
{
["input"] = summaryText,
["topic"] = "people and places",
["example_entities"] = "John, Jane, mother, brother, Paris, Rome"
};
var extractionResult = (await kernel.InvokeAsync(entityExtraction, variables)).ToString();
Console.WriteLine("======== Extract Entities ========");
Console.WriteLine(extractionResult);
variables["input"] = extractionResult;
variables["reference_context"] = GroundingText;
var groundingResult = (await kernel.InvokeAsync(reference_check, variables)).ToString();
Console.WriteLine("\n======== Reference Check ========");
Console.WriteLine(groundingResult);
variables["input"] = summaryText;
variables["ungrounded_entities"] = groundingResult;
var excisionResult = await kernel.InvokeAsync(entity_excision, variables);
Console.WriteLine("\n======== Excise Entities ========");
Console.WriteLine(excisionResult.GetValue<string>());
}
public static async Task PlanningWithGroundednessAsync()
{
var targetTopic = "people and places";
var samples = "John, Jane, mother, brother, Paris, Rome";
var ask = @$"Make a summary of the following text. Then make a list of entities
related to {targetTopic} (such as {samples}) which are present in the summary.
Take this list of entities, and from it make another list of those which are not
grounded in the original input text. Finally, rewrite your summary to remove the entities
which are not grounded in the original.
Text:\n{GroundingText};
";
Console.WriteLine("\n======== Planning - Groundedness Checks ========");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
string folder = RepoFiles.SamplePluginsPath();
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "SummarizePlugin"));
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "GroundingPlugin"));
kernel.ImportPluginFromType<TextPlugin>();
var planner = new HandlebarsPlanner();
var plan = await planner.CreatePlanAsync(kernel, ask);
Console.WriteLine($"======== Goal: ========\n{ask}");
Console.WriteLine($"======== Plan ========\n{plan}");
var result = await plan.InvokeAsync(kernel);
Console.WriteLine("======== Result ========");
Console.WriteLine(result);
}
}
@@ -1,18 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/**
* Logit_bias is an optional parameter that modifies the likelihood of specified tokens appearing in a Completion.
* When using the Token Selection Biases parameter, the bias is added to the logits generated by the model prior to sampling.
*/
public static class Example49_LogitBias
public class Example49_LogitBias : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
OpenAIChatCompletionService chatCompletionService = new(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
@@ -23,14 +27,15 @@ public static class Example49_LogitBias
// "novel literature reading author library story chapter paperback hardcover ebook publishing fiction nonfiction manuscript textbook bestseller bookstore reading list bookworm"
var keys = new[] { 3919, 626, 17201, 1300, 25782, 9800, 32016, 13571, 43582, 20189, 1891, 10424, 9631, 16497, 12984, 20020, 24046, 13159, 805, 15817, 5239, 2070, 13466, 32932, 8095, 1351, 25323 };
var settings = new OpenAIPromptExecutionSettings();
var settings = new OpenAIPromptExecutionSettings
{
// This will make the model try its best to avoid any of the above related words.
//-100 to potentially ban all the tokens from the list.
TokenSelectionBiases = keys.ToDictionary(key => key, key => -100)
};
// This will make the model try its best to avoid any of the above related words.
//-100 to potentially ban all the tokens from the list.
settings.TokenSelectionBiases = keys.ToDictionary(key => key, key => -100);
Console.WriteLine("Chat content:");
Console.WriteLine("------------------------");
WriteLine("Chat content:");
WriteLine("------------------------");
var chatHistory = new ChatHistory("You are a librarian expert");
@@ -66,13 +71,17 @@ public static class Example49_LogitBias
/// <summary>
/// Outputs the last message of the chat history
/// </summary>
private static Task MessageOutputAsync(ChatHistory chatHistory)
private Task MessageOutputAsync(ChatHistory chatHistory)
{
var message = chatHistory.Last();
Console.WriteLine($"{message.Role}: {message.Content}");
Console.WriteLine("------------------------");
WriteLine($"{message.Role}: {message.Content}");
WriteLine("------------------------");
return Task.CompletedTask;
}
public Example49_LogitBias(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,92 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using RepoUtils;
public static class Example52_ApimAuth
{
public static async Task RunAsync()
{
// Azure API Management details
// For more information see 'Protect your Azure OpenAI API keys with Azure API Management' here: https://learn.microsoft.com/en-us/semantic-kernel/deploy/
var apimUri = new Uri(Env.Var("Apim__Endpoint"));
var subscriptionKey = Env.Var("Apim__SubscriptionKey");
// Use interactive browser login
string[] scopes = new string[] { "https://cognitiveservices.azure.com/.default" };
var credential = new InteractiveBrowserCredential();
var requestContext = new TokenRequestContext(scopes);
var accessToken = await credential.GetTokenAsync(requestContext);
// Create HttpClient and include subscription key as a default header
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
// Configure OpenAIClient to use
// - Custom HttpClient with subscription key header
// - Diagnostics to log error response headers from APIM to aid problem determination
// - Authentication using BearerTokenCredential retrieved via interactive browser login
var clientOptions = new OpenAIClientOptions
{
Transport = new HttpClientTransport(httpClient),
Diagnostics =
{
LoggedHeaderNames = { "ErrorSource", "ErrorReason", "ErrorMessage", "ErrorScope", "ErrorSection", "ErrorStatusCode" },
}
};
var openAIClient = new OpenAIClient(apimUri, new BearerTokenCredential(accessToken), clientOptions);
IKernelBuilder builder = Kernel.CreateBuilder();
builder.Services.AddLogging(c => c.SetMinimumLevel(LogLevel.Warning).AddConsole());
builder.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
openAIClient: openAIClient);
Kernel kernel = builder.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>());
httpClient.Dispose();
}
}
public class BearerTokenCredential : TokenCredential
{
private readonly AccessToken _accessToken;
// Constructor that takes a Bearer token string and its expiration date
public BearerTokenCredential(AccessToken accessToken)
{
this._accessToken = accessToken;
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return this._accessToken;
}
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new ValueTask<AccessToken>(this._accessToken);
}
}
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Azure;
using Azure.AI.OpenAI;
using Azure.Core.Pipeline;
using Microsoft.SemanticKernel;
using RepoUtils;
public static class Example52_CustomOpenAIClient
{
public static async Task RunAsync()
{
Console.WriteLine("======== Using a custom OpenAI client ========");
string endpoint = TestConfiguration.AzureOpenAI.Endpoint;
string deploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
if (endpoint is null || deploymentName is null || apiKey is null)
{
Console.WriteLine("Azure OpenAI credentials not found. Skipping example.");
return;
}
// Create an HttpClient and include your custom header(s)
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("x-my-custom-header", "My custom value");
// Configure OpenAIClient to use the customized HttpClient
var clientOptions = new OpenAIClientOptions
{
Transport = new HttpClientTransport(httpClient),
};
var openAIClient = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey), clientOptions);
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(deploymentName, openAIClient);
Kernel kernel = builder.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>());
httpClient.Dispose();
}
}
@@ -5,6 +5,10 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using xRetry;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// This example demonstrates how to use Azure OpenAI Chat Completion with data.
@@ -24,17 +28,12 @@ using Microsoft.SemanticKernel.Connectors.OpenAI;
/// <para> dotnet user-secrets set "AzureAISearch:ApiKey" "{Key from your Search service resource}"</para>
/// <para> dotnet user-secrets set "AzureAISearch:IndexName" "..."</para>
/// </value>
public static class Example54_AzureChatCompletionWithData
public class Example54_AzureChatCompletionWithData : BaseTest
{
public static async Task RunAsync()
[RetryFact(typeof(HttpOperationException))]
public async Task ExampleWithChatCompletionAsync()
{
await ExampleWithChatCompletionAsync();
await ExampleWithKernelAsync();
}
private static async Task ExampleWithChatCompletionAsync()
{
Console.WriteLine("=== Example with Chat Completion ===");
WriteLine("=== Example with Chat Completion ===");
var chatCompletion = new AzureOpenAIChatCompletionWithDataService(GetCompletionWithDataConfig());
var chatHistory = new ChatHistory();
@@ -52,9 +51,9 @@ public static class Example54_AzureChatCompletionWithData
// 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}");
Console.WriteLine();
WriteLine($"Ask: {ask}");
WriteLine($"Response: {response}");
WriteLine();
// Chat history maintenance
if (!string.IsNullOrEmpty(toolResponse))
@@ -69,20 +68,21 @@ public static class Example54_AzureChatCompletionWithData
chatHistory.AddUserMessage(ask);
// Chat Completion Streaming example
Console.WriteLine($"Ask: {ask}");
Console.WriteLine("Response: ");
WriteLine($"Ask: {ask}");
WriteLine("Response: ");
await foreach (var word in chatCompletion.GetStreamingChatMessageContentsAsync(chatHistory))
{
Console.Write(word);
Write(word);
}
Console.WriteLine(Environment.NewLine);
WriteLine(Environment.NewLine);
}
private static async Task ExampleWithKernelAsync()
[RetryFact(typeof(HttpOperationException))]
public async Task ExampleWithKernelAsync()
{
Console.WriteLine("=== Example with Kernel ===");
WriteLine("=== Example with Kernel ===");
var ask = "How did Emily and David meet?";
@@ -100,9 +100,9 @@ public static class Example54_AzureChatCompletionWithData
// 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();
WriteLine($"Ask: {ask}");
WriteLine($"Response: {response.GetValue<string>()}");
WriteLine();
// Second question based on uploaded content.
ask = "What are Emily and David studying?";
@@ -112,9 +112,9 @@ public static class Example54_AzureChatCompletionWithData
// 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();
WriteLine($"Ask: {ask}");
WriteLine($"Response: {response.GetValue<string>()}");
WriteLine();
}
/// <summary>
@@ -132,4 +132,8 @@ public static class Example54_AzureChatCompletionWithData
DataSourceIndex = TestConfiguration.AzureAISearch.IndexName
};
}
public Example54_AzureChatCompletionWithData(ITestOutputHelper output) : base(output)
{
}
}
@@ -4,49 +4,23 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.DeepDev;
using Microsoft.ML.Tokenizers;
using Microsoft.SemanticKernel.Text;
using Resources;
using SharpToken;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.SemanticKernel.Text.TextChunker;
public static class Example55_TextChunker
namespace Examples;
public class Example55_TextChunker : BaseTest
{
private const string Text = @"The city of Venice, located in the northeastern part of Italy,
is renowned for its unique geographical features. Built on more than 100 small islands in a lagoon in the
Adriatic Sea, it has no roads, just canals including the Grand Canal thoroughfare lined with Renaissance and
Gothic palaces. The central square, Piazza San Marco, contains St. Mark's Basilica, which is tiled with Byzantine
mosaics, and the Campanile bell tower offering views of the city's red roofs.
The Amazon Rainforest, also known as Amazonia, is a moist broadleaf tropical rainforest in the Amazon biome that
covers most of the Amazon basin of South America. This basin encompasses 7 million square kilometers, of which
5.5 million square kilometers are covered by the rainforest. This region includes territory belonging to nine nations
and 3.4 million square kilometers of uncontacted tribes. The Amazon represents over half of the planet's remaining
rainforests and comprises the largest and most biodiverse tract of tropical rainforest in the world.
The Great Barrier Reef is the world's largest coral reef system composed of over 2,900 individual reefs and 900 islands
stretching for over 2,300 kilometers over an area of approximately 344,400 square kilometers. The reef is located in the
Coral Sea, off the coast of Queensland, Australia. The Great Barrier Reef can be seen from outer space and is the world's
biggest single structure made by living organisms. This reef structure is composed of and built by billions of tiny organisms,
known as coral polyps.";
public static Task RunAsync()
[Fact]
public void RunExample()
{
RunExample();
RunExampleForTokenCounterType(TokenCounterType.SharpToken);
RunExampleForTokenCounterType(TokenCounterType.MicrosoftML);
RunExampleForTokenCounterType(TokenCounterType.MicrosoftMLRoberta);
RunExampleForTokenCounterType(TokenCounterType.DeepDev);
RunExampleWithHeader();
return Task.CompletedTask;
}
private static void RunExample()
{
Console.WriteLine("=== Text chunking ===");
WriteLine("=== Text chunking ===");
var lines = TextChunker.SplitPlainTextLines(Text, 40);
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 120);
@@ -54,9 +28,14 @@ known as coral polyps.";
WriteParagraphsToConsole(paragraphs);
}
private static void RunExampleForTokenCounterType(TokenCounterType counterType)
[Theory]
[InlineData(TokenCounterType.SharpToken)]
[InlineData(TokenCounterType.MicrosoftML)]
[InlineData(TokenCounterType.MicrosoftMLRoberta)]
[InlineData(TokenCounterType.DeepDev)]
public void RunExampleForTokenCounterType(TokenCounterType counterType)
{
Console.WriteLine($"=== Text chunking with a custom({counterType}) token counter ===");
WriteLine($"=== Text chunking with a custom({counterType}) token counter ===");
var sw = new Stopwatch();
sw.Start();
var tokenCounter = s_tokenCounterFactory(counterType);
@@ -65,13 +44,14 @@ known as coral polyps.";
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 120, tokenCounter: tokenCounter);
sw.Stop();
Console.WriteLine($"Elapsed time: {sw.ElapsedMilliseconds} ms");
WriteLine($"Elapsed time: {sw.ElapsedMilliseconds} ms");
WriteParagraphsToConsole(paragraphs);
}
private static void RunExampleWithHeader()
[Fact]
public void RunExampleWithHeader()
{
Console.WriteLine("=== Text chunking with chunk header ===");
WriteLine("=== Text chunking with chunk header ===");
var lines = TextChunker.SplitPlainTextLines(Text, 40);
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 150, chunkHeader: "DOCUMENT NAME: test.txt\n\n");
@@ -79,20 +59,20 @@ known as coral polyps.";
WriteParagraphsToConsole(paragraphs);
}
private static void WriteParagraphsToConsole(List<string> paragraphs)
private void WriteParagraphsToConsole(List<string> paragraphs)
{
for (var i = 0; i < paragraphs.Count; i++)
{
Console.WriteLine(paragraphs[i]);
WriteLine(paragraphs[i]);
if (i < paragraphs.Count - 1)
{
Console.WriteLine("------------------------");
WriteLine("------------------------");
}
}
}
private enum TokenCounterType
public enum TokenCounterType
{
SharpToken,
MicrosoftML,
@@ -175,4 +155,26 @@ known as coral polyps.";
TokenCounterType.MicrosoftMLRoberta => (string input) => MicrosoftMLRobertaTokenCounter(input),
_ => throw new ArgumentOutOfRangeException(nameof(counterType), counterType, null),
};
private const string Text = @"The city of Venice, located in the northeastern part of Italy,
is renowned for its unique geographical features. Built on more than 100 small islands in a lagoon in the
Adriatic Sea, it has no roads, just canals including the Grand Canal thoroughfare lined with Renaissance and
Gothic palaces. The central square, Piazza San Marco, contains St. Mark's Basilica, which is tiled with Byzantine
mosaics, and the Campanile bell tower offering views of the city's red roofs.
The Amazon Rainforest, also known as Amazonia, is a moist broadleaf tropical rainforest in the Amazon biome that
covers most of the Amazon basin of South America. This basin encompasses 7 million square kilometers, of which
5.5 million square kilometers are covered by the rainforest. This region includes territory belonging to nine nations
and 3.4 million square kilometers of uncontacted tribes. The Amazon represents over half of the planet's remaining
rainforests and comprises the largest and most biodiverse tract of tropical rainforest in the world.
The Great Barrier Reef is the world's largest coral reef system composed of over 2,900 individual reefs and 900 islands
stretching for over 2,300 kilometers over an area of approximately 344,400 square kilometers. The reef is located in the
Coral Sea, off the coast of Queensland, Australia. The Great Barrier Reef can be seen from outer space and is the world's
biggest single structure made by living organisms. This reef structure is composed of and built by billions of tiny organisms,
known as coral polyps.";
public Example55_TextChunker(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,22 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Plugins.Core;
using Xunit;
using Xunit.Abstractions;
public static class Example56_TemplateMethodFunctionsWithMultipleArguments
namespace Examples;
public class Example56_TemplateMethodFunctionsWithMultipleArguments : BaseTest
{
/// <summary>
/// Show how to invoke a Method Function written in C# with multiple arguments
/// from a Prompt Function written in natural language
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== TemplateMethodFunctionsWithMultipleArguments ========");
WriteLine("======== TemplateMethodFunctionsWithMultipleArguments ========");
string serviceId = TestConfiguration.AzureOpenAI.ServiceId;
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
@@ -26,7 +30,7 @@ public static class Example56_TemplateMethodFunctionsWithMultipleArguments
if (apiKey == null || deploymentName == null || modelId == null || endpoint == null)
{
Console.WriteLine("AzureOpenAI modelId, endpoint, apiKey, or deploymentName not found. Skipping example.");
WriteLine("AzureOpenAI modelId, endpoint, apiKey, or deploymentName not found. Skipping example.");
return;
}
@@ -53,19 +57,19 @@ public static class Example56_TemplateMethodFunctionsWithMultipleArguments
";
// This allows to see the prompt before it's sent to OpenAI
Console.WriteLine("--- Rendered Prompt");
WriteLine("--- Rendered Prompt");
var promptTemplateFactory = new KernelPromptTemplateFactory();
var promptTemplate = promptTemplateFactory.Create(new PromptTemplateConfig(FunctionDefinition));
var renderedPrompt = await promptTemplate.RenderAsync(kernel, arguments);
Console.WriteLine(renderedPrompt);
WriteLine(renderedPrompt);
// Run the prompt / prompt function
var haiku = kernel.CreateFunctionFromPrompt(FunctionDefinition, new OpenAIPromptExecutionSettings() { MaxTokens = 100 });
// Show the result
Console.WriteLine("--- Prompt Function result");
WriteLine("--- Prompt Function result");
var result = await kernel.InvokeAsync(haiku, arguments);
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
/* OUTPUT:
@@ -79,4 +83,8 @@ Wizarding world he explores,
Harry Potter's tale.
*/
}
public Example56_TemplateMethodFunctionsWithMultipleArguments(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,55 +1,32 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Example57_KernelHooks
namespace Examples;
public class Example57_KernelHooks : BaseTest
{
private static string? s_openAIModelId;
private static string? s_openAIApiKey;
public static async Task RunAsync()
{
Console.WriteLine("\n======== Using Function Execution Handlers ========\n");
s_openAIModelId = TestConfiguration.OpenAI.ChatModelId;
s_openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (s_openAIModelId == null || s_openAIApiKey == null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
await GetUsageAsync();
await GetRenderedPromptAsync();
await ChangingResultAsync();
await BeforeInvokeCancellationAsync();
await AfterInvokeCancellationAsync();
}
/// <summary>
/// Demonstrate using kernel invocation-hooks to monitor usage:
/// <see cref="Kernel.FunctionInvoking"/>
/// <see cref="Kernel.FunctionInvoked"/>
/// </summary>
private static async Task GetUsageAsync()
[Fact]
public async Task GetUsageAsync()
{
Console.WriteLine("\n======== Get Usage Data ========\n");
WriteLine("\n======== Get Usage Data ========\n");
// Create kernel instance
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: s_openAIModelId!,
apiKey: s_openAIApiKey!)
modelId: _openAIModelId!,
apiKey: _openAIApiKey!)
.Build();
// Initialize prompt
@@ -63,18 +40,18 @@ public static class Example57_KernelHooks
// Define hooks
void MyPreHandler(object? sender, FunctionInvokingEventArgs e)
{
Console.WriteLine($"{e.Function.Name} : Pre Execution Handler - Triggered");
WriteLine($"{e.Function.Name} : Pre Execution Handler - Triggered");
}
void MyRemovedPreExecutionHandler(object? sender, FunctionInvokingEventArgs e)
{
Console.WriteLine($"{e.Function.Name} : Pre Execution Handler - Should not trigger");
WriteLine($"{e.Function.Name} : Pre Execution Handler - Should not trigger");
e.Cancel = true;
}
void MyPostExecutionHandler(object? sender, FunctionInvokedEventArgs e)
{
Console.WriteLine($"{e.Function.Name} : Post Execution Handler - Usage: {e.Result.Metadata?["Usage"]?.AsJson()}");
WriteLine($"{e.Function.Name} : Post Execution Handler - Usage: {e.Result.Metadata?["Usage"]?.AsJson()}");
}
kernel.FunctionInvoking += MyPreHandler;
@@ -88,7 +65,7 @@ public static class Example57_KernelHooks
// Invoke prompt to trigger execution hooks.
const string Input = "I missed the F1 final race";
var result = await kernel.InvokeAsync(excuseFunction, new() { ["input"] = Input });
Console.WriteLine($"Function Result: {result}");
WriteLine($"Function Result: {result}");
}
/// <summary>
@@ -96,15 +73,16 @@ public static class Example57_KernelHooks
/// <see cref="Kernel.PromptRendering"/>
/// <see cref="Kernel.PromptRendered"/>
/// </summary>
private static async Task GetRenderedPromptAsync()
[Fact]
public async Task GetRenderedPromptAsync()
{
Console.WriteLine("\n======== Get Rendered Prompt ========\n");
WriteLine("\n======== Get Rendered Prompt ========\n");
// Create kernel instance
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: s_openAIModelId!,
apiKey: s_openAIApiKey!)
modelId: _openAIModelId!,
apiKey: _openAIApiKey!)
.Build();
// Initialize prompt
@@ -118,16 +96,16 @@ public static class Example57_KernelHooks
// Define hooks
void MyRenderingHandler(object? sender, PromptRenderingEventArgs e)
{
Console.WriteLine($"{e.Function.Name} : Prompt Rendering Handler - Triggered");
WriteLine($"{e.Function.Name} : Prompt Rendering Handler - Triggered");
e.Arguments["style"] = "Seinfeld";
}
void MyRenderedHandler(object? sender, PromptRenderedEventArgs e)
{
Console.WriteLine($"{e.Function.Name} : Prompt Rendered Handler - Triggered");
WriteLine($"{e.Function.Name} : Prompt Rendered Handler - Triggered");
e.RenderedPrompt += " USE SHORT, CLEAR, COMPLETE SENTENCES.";
Console.WriteLine(e.RenderedPrompt);
WriteLine(e.RenderedPrompt);
}
kernel.PromptRendering += MyRenderingHandler;
@@ -136,22 +114,23 @@ public static class Example57_KernelHooks
// Invoke prompt to trigger prompt rendering hooks.
const string Input = "I missed the F1 final race";
var result = await kernel.InvokeAsync(excuseFunction, new() { ["input"] = Input });
Console.WriteLine($"Function Result: {result.GetValue<string>()}");
WriteLine($"Function Result: {result.GetValue<string>()}");
}
/// <summary>
/// Demonstrate using kernel invocation-hooks to post process result:
/// <see cref="Kernel.FunctionInvoked"/>
/// </summary>
private static async Task ChangingResultAsync()
[Fact]
public async Task ChangingResultAsync()
{
Console.WriteLine("\n======== Changing/Filtering Function Result ========\n");
WriteLine("\n======== Changing/Filtering Function Result ========\n");
// Create kernel instance
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: s_openAIModelId!,
apiKey: s_openAIApiKey!)
modelId: _openAIModelId!,
apiKey: _openAIApiKey!)
.Build();
// Initialize function
@@ -178,7 +157,7 @@ public static class Example57_KernelHooks
// Invoke prompt to trigger execution hooks.
var result = await kernel.InvokeAsync(writerFunction);
Console.WriteLine($"Function Result: {result.GetValue<string>()}");
WriteLine($"Function Result: {result.GetValue<string>()}");
}
/// <summary>
@@ -186,15 +165,16 @@ public static class Example57_KernelHooks
/// <see cref="Kernel.FunctionInvoking"/>
/// <see cref="Kernel.FunctionInvoked"/>
/// </summary>
private static async Task BeforeInvokeCancellationAsync()
[Fact]
public async Task BeforeInvokeCancellationAsync()
{
Console.WriteLine("\n======== Cancelling Pipeline Execution - Invoking event ========\n");
WriteLine("\n======== Cancelling Pipeline Execution - Invoking event ========\n");
// Create kernel instance
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: s_openAIModelId!,
apiKey: s_openAIApiKey!)
modelId: _openAIModelId!,
apiKey: _openAIApiKey!)
.Build();
// Initialize prompt
@@ -208,7 +188,7 @@ public static class Example57_KernelHooks
// Adding new inline handler to cancel/prevent function execution
kernel.FunctionInvoking += (object? sender, FunctionInvokingEventArgs e) =>
{
Console.WriteLine($"{e.Function.Name} : FunctionInvoking - Cancelling before execution");
WriteLine($"{e.Function.Name} : FunctionInvoking - Cancelling before execution");
e.Cancel = true;
};
@@ -220,8 +200,16 @@ public static class Example57_KernelHooks
};
// Invoke prompt to trigger execution hooks.
var result = await kernel.InvokeAsync(writerFunction);
Console.WriteLine($"Function Invocation Times: {functionInvokedCount}");
try
{
var result = await kernel.InvokeAsync(writerFunction);
}
catch (KernelFunctionCanceledException fcex)
{
WriteLine(fcex.Message);
}
WriteLine($"Function Invocation Times: {functionInvokedCount}");
}
/// <summary>
@@ -229,15 +217,16 @@ public static class Example57_KernelHooks
/// <see cref="Kernel.FunctionInvoking"/>
/// <see cref="Kernel.FunctionInvoked"/>
/// </summary>
private static async Task AfterInvokeCancellationAsync()
[Fact]
public async Task AfterInvokeCancellationAsync()
{
Console.WriteLine("\n======== Cancelling Pipeline Execution - Invoked event ========\n");
WriteLine("\n======== Cancelling Pipeline Execution - Invoked event ========\n");
// Create kernel instance
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: s_openAIModelId!,
apiKey: s_openAIApiKey!)
modelId: _openAIModelId!,
apiKey: _openAIApiKey!)
.Build();
// Initialize prompts
@@ -261,8 +250,31 @@ public static class Example57_KernelHooks
};
// Invoke prompt to trigger execution hooks.
var result = await kernel.InvokeAsync(secondFunction);
Console.WriteLine($"Function Invoked Times: {functionInvokedCount}");
Console.WriteLine($"Function Invoking Times: {functionInvokingCount}");
try
{
var result = await kernel.InvokeAsync(secondFunction);
}
catch (KernelFunctionCanceledException fcex)
{
WriteLine(fcex.Message);
}
WriteLine($"Function Invoked Times: {functionInvokedCount}");
WriteLine($"Function Invoking Times: {functionInvokingCount}");
}
private readonly string? _openAIModelId;
private readonly string? _openAIApiKey;
public Example57_KernelHooks(ITestOutputHelper output) : base(output)
{
this._openAIModelId = TestConfiguration.OpenAI.ChatModelId;
this._openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (this._openAIModelId == null || this._openAIApiKey == null)
{
WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
}
}
@@ -5,8 +5,11 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit.Abstractions;
public static class Example58_ConfigureExecutionSettings
namespace Examples;
public class Example58_ConfigureExecutionSettings : BaseTest
{
/// <summary>
/// Show how to configure model execution settings
@@ -94,4 +97,8 @@ Hello! As an AI language model, I can help you with a variety of tasks, includin
4. Providing explanations and
*/
}
public Example58_ConfigureExecutionSettings(ITestOutputHelper output) : base(output)
{
}
}
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
@@ -12,11 +11,16 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use OpenAI's tool calling capability via the chat completions interface.
public static class Example59_OpenAIFunctionCalling
public class Example59_OpenAIFunctionCalling : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create kernel.
IKernelBuilder builder = Kernel.CreateBuilder();
@@ -46,24 +50,24 @@ public static class Example59_OpenAIFunctionCalling
}, "Get_Weather_For_City", "Gets the current weather for the specified city"),
});
Console.WriteLine("======== Example 1: Use automated function calling with a non-streaming prompt ========");
WriteLine("======== Example 1: Use automated function calling with a non-streaming prompt ========");
{
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
Console.WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
Console.WriteLine();
WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
WriteLine();
}
Console.WriteLine("======== Example 2: Use automated function calling with a streaming prompt ========");
WriteLine("======== Example 2: Use automated function calling with a streaming prompt ========");
{
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
await foreach (var update in kernel.InvokePromptStreamingAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)))
{
Console.Write(update);
Write(update);
}
Console.WriteLine();
WriteLine();
}
Console.WriteLine("======== Example 3: Use manual function calling with a non-streaming prompt ========");
WriteLine("======== Example 3: Use manual function calling with a non-streaming prompt ========");
{
var chat = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory();
@@ -76,7 +80,7 @@ public static class Example59_OpenAIFunctionCalling
if (result.Content is not null)
{
Console.Write(result.Content);
Write(result.Content);
}
List<ChatCompletionsFunctionToolCall> toolCalls = result.ToolCalls.OfType<ChatCompletionsFunctionToolCall>().ToList();
@@ -99,9 +103,10 @@ public static class Example59_OpenAIFunctionCalling
}
}
Console.WriteLine();
WriteLine();
}
/* Uncomment this to try in a console chat loop.
Console.WriteLine("======== Example 4: Use automated function calling with a streaming chat ========");
{
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
@@ -130,6 +135,10 @@ public static class Example59_OpenAIFunctionCalling
chatHistory.AddAssistantMessage(sb.ToString());
Console.WriteLine();
}
}
}*/
}
public Example59_OpenAIFunctionCalling(ITestOutputHelper output) : base(output)
{
}
}
@@ -6,23 +6,23 @@ using System.Globalization;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows different ways how to define and execute method functions using custom and primitive types.
public static class Example60_AdvancedMethodFunctions
public class Example60_AdvancedMethodFunctions : BaseTest
{
public static async Task RunAsync()
{
await MethodFunctionsChainingAsync();
}
#region Method Functions Chaining
/// <summary>
/// This example executes Function1, which in turn executes Function2.
/// </summary>
private static async Task MethodFunctionsChainingAsync()
[Fact]
public async Task MethodFunctionsChainingAsync()
{
Console.WriteLine("Running Method Function Chaining example...");
WriteLine("Running Method Function Chaining example...");
var kernel = new Kernel();
@@ -30,8 +30,8 @@ public static class Example60_AdvancedMethodFunctions
var customType = await kernel.InvokeAsync<MyCustomType>(functions["Function1"]);
Console.WriteLine($"CustomType.Number: {customType!.Number}"); // 2
Console.WriteLine($"CustomType.Text: {customType.Text}"); // From Function1 + From Function2
WriteLine($"CustomType.Number: {customType!.Number}"); // 2
WriteLine($"CustomType.Text: {customType.Text}"); // From Function1 + From Function2
}
/// <summary>
@@ -39,7 +39,7 @@ public static class Example60_AdvancedMethodFunctions
/// </summary>
private sealed class FunctionsChainingPlugin
{
public const string PluginName = nameof(FunctionsChainingPlugin);
private const string PluginName = nameof(FunctionsChainingPlugin);
[KernelFunction]
public async Task<MyCustomType> Function1Async(Kernel kernel)
@@ -115,4 +115,8 @@ public static class Example60_AdvancedMethodFunctions
}
#endregion
public Example60_AdvancedMethodFunctions(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,60 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using xRetry;
using Xunit.Abstractions;
public static class Example61_MultipleLLMs
namespace Examples;
public class Example61_MultipleLLMs : BaseTest
{
/// <summary>
/// Show how to run a prompt function and specify a specific service to use.
/// </summary>
public static async Task RunAsync()
[RetryFact(typeof(HttpOperationException))]
public async Task RunAsync()
{
Console.WriteLine("======== Example61_MultipleLLMs ========");
string azureApiKey = TestConfiguration.AzureOpenAI.ApiKey;
string azureDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string azureModelId = TestConfiguration.AzureOpenAI.ChatModelId;
string azureEndpoint = TestConfiguration.AzureOpenAI.Endpoint;
if (azureApiKey == null || azureDeploymentName == null || azureModelId == null || azureEndpoint == null)
{
Console.WriteLine("AzureOpenAI endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
return;
}
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (openAIModelId == null || openAIApiKey == null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
Kernel kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: azureDeploymentName,
endpoint: azureEndpoint,
apiKey: azureApiKey,
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
serviceId: "AzureOpenAIChat",
modelId: azureModelId)
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey,
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey,
serviceId: "OpenAIChat")
.Build();
await RunByServiceIdAsync(kernel, "AzureOpenAIChat");
await RunByModelIdAsync(kernel, openAIModelId);
await RunByFirstModelIdAsync(kernel, "gpt-4-1106-preview", azureModelId, openAIModelId);
await RunByModelIdAsync(kernel, TestConfiguration.OpenAI.ChatModelId);
await RunByFirstModelIdAsync(kernel, "gpt-4-1106-preview", TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.OpenAI.ChatModelId);
}
public static async Task RunByServiceIdAsync(Kernel kernel, string serviceId)
private async Task RunByServiceIdAsync(Kernel kernel, string serviceId)
{
Console.WriteLine($"======== Service Id: {serviceId} ========");
WriteLine($"======== Service Id: {serviceId} ========");
var prompt = "Hello AI, what can you do for me?";
@@ -64,12 +46,12 @@ public static class Example61_MultipleLLMs
{ serviceId, new PromptExecutionSettings() }
};
var result = await kernel.InvokePromptAsync(prompt, arguments);
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
public static async Task RunByModelIdAsync(Kernel kernel, string modelId)
private async Task RunByModelIdAsync(Kernel kernel, string modelId)
{
Console.WriteLine($"======== Model Id: {modelId} ========");
WriteLine($"======== Model Id: {modelId} ========");
var prompt = "Hello AI, what can you do for me?";
@@ -79,12 +61,12 @@ public static class Example61_MultipleLLMs
{
ModelId = modelId
}));
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
public static async Task RunByFirstModelIdAsync(Kernel kernel, params string[] modelIds)
private async Task RunByFirstModelIdAsync(Kernel kernel, params string[] modelIds)
{
Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========");
var prompt = "Hello AI, what can you do for me?";
@@ -98,6 +80,10 @@ public static class Example61_MultipleLLMs
var function = kernel.CreateFunctionFromPrompt(promptConfig);
var result = await kernel.InvokeAsync(function);
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
public Example61_MultipleLLMs(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,55 +7,40 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Services;
using Xunit;
using Xunit.Abstractions;
public static class Example62_CustomAIServiceSelector
namespace Examples;
public class Example62_CustomAIServiceSelector : BaseTest
{
/// <summary>
/// Show how to use a custom AI service selector to select a specific model
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Example62_CustomAIServiceSelector ========");
string azureApiKey = TestConfiguration.AzureOpenAI.ApiKey;
string azureDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string azureModelId = TestConfiguration.AzureOpenAI.ChatModelId;
string azureEndpoint = TestConfiguration.AzureOpenAI.Endpoint;
if (azureApiKey == null || azureDeploymentName == null || azureModelId == null || azureEndpoint == null)
{
Console.WriteLine("AzureOpenAI endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
return;
}
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
if (openAIModelId == null || openAIApiKey == null)
{
Console.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}
WriteLine("======== Example62_CustomAIServiceSelector ========");
// Build a kernel with multiple chat completion services
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: azureDeploymentName,
endpoint: azureEndpoint,
apiKey: azureApiKey,
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
serviceId: "AzureOpenAIChat",
modelId: azureModelId)
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey,
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey,
serviceId: "OpenAIChat");
builder.Services.AddSingleton<IAIServiceSelector>(new GptAIServiceSelector()); // Use the custom AI service selector to select the GPT model
builder.Services.AddSingleton<IAIServiceSelector>(new GptAIServiceSelector(this.Output)); // Use the custom AI service selector to select the GPT model
Kernel kernel = builder.Build();
// This invocation is done with the model selected by the custom selector
var prompt = "Hello AI, what can you do for me?";
var result = await kernel.InvokePromptAsync(prompt);
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
/// <summary>
@@ -66,6 +51,13 @@ public static class Example62_CustomAIServiceSelector
/// </summary>
private sealed class GptAIServiceSelector : IAIServiceSelector
{
private readonly ITestOutputHelper _output;
public GptAIServiceSelector(ITestOutputHelper output)
{
this._output = output;
}
public bool TrySelectAIService<T>(
Kernel kernel, KernelFunction function, KernelArguments arguments,
[NotNullWhen(true)] out T? service, out PromptExecutionSettings? serviceSettings) where T : class, IAIService
@@ -77,7 +69,7 @@ public static class Example62_CustomAIServiceSelector
var endpoint = serviceToCheck.GetEndpoint();
if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"Selected model: {serviceModelId} {endpoint}");
this._output.WriteLine($"Selected model: {serviceModelId} {endpoint}");
service = serviceToCheck;
serviceSettings = new OpenAIPromptExecutionSettings();
return true;
@@ -89,4 +81,8 @@ public static class Example62_CustomAIServiceSelector
return false;
}
}
public Example62_CustomAIServiceSelector(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,13 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use chat completion standardized prompts.
public static class Example63_ChatCompletionPrompts
public class Example63_ChatCompletionPrompts : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
const string ChatPrompt = @"
<message role=""user"">What is Seattle?</message>
@@ -23,17 +27,21 @@ public static class Example63_ChatCompletionPrompts
var chatSemanticFunction = kernel.CreateFunctionFromPrompt(ChatPrompt);
var chatPromptResult = await kernel.InvokeAsync(chatSemanticFunction);
Console.WriteLine("Chat Prompt:");
Console.WriteLine(ChatPrompt);
Console.WriteLine("Chat Prompt Result:");
Console.WriteLine(chatPromptResult);
WriteLine("Chat Prompt:");
WriteLine(ChatPrompt);
WriteLine("Chat Prompt Result:");
WriteLine(chatPromptResult);
Console.WriteLine("Chat Prompt Streaming Result:");
WriteLine("Chat Prompt Streaming Result:");
string completeMessage = string.Empty;
await foreach (var message in kernel.InvokeStreamingAsync<string>(chatSemanticFunction))
{
Console.Write(message);
completeMessage += message;
Write(message);
}
Console.WriteLine();
WriteLine("---------- Streamed Content ----------");
WriteLine(completeMessage);
/*
Chat Prompt:
@@ -51,4 +59,8 @@ public static class Example63_ChatCompletionPrompts
}
*/
}
public Example63_ChatCompletionPrompts(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,54 +1,46 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using xRetry;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use multiple prompt template formats.
public static class Example64_MultiplePromptTemplates
public class Example64_MultiplePromptTemplates : BaseTest
{
/// <summary>
/// Show how to combine multiple prompt template factories.
/// </summary>
public static async Task RunAsync()
[RetryTheory(typeof(HttpOperationException))]
[InlineData("semantic-kernel", "Hello AI, my name is {{$name}}. What is the origin of my name?")]
[InlineData("handlebars", "Hello AI, my name is {{name}}. What is the origin of my name?")]
public Task RunAsync(string templateFormat, string prompt)
{
Console.WriteLine("======== Example64_MultiplePromptTemplates ========");
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
string chatModelId = TestConfiguration.AzureOpenAI.ChatModelId;
string endpoint = TestConfiguration.AzureOpenAI.Endpoint;
if (apiKey == null || chatDeploymentName == null || endpoint == null || chatModelId == null)
{
Console.WriteLine("Azure endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
return;
}
WriteLine("======== Example64_MultiplePromptTemplates ========");
Kernel kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: chatDeploymentName,
endpoint: endpoint,
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
serviceId: "AzureOpenAIChat",
apiKey: apiKey,
modelId: chatModelId)
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.Build();
var promptTemplateFactory = new AggregatorPromptTemplateFactory(
new KernelPromptTemplateFactory(),
new HandlebarsPromptTemplateFactory());
var skPrompt = "Hello AI, my name is {{$name}}. What is the origin of my name?";
var handlebarsPrompt = "Hello AI, my name is {{name}}. What is the origin of my name?";
await RunPromptAsync(kernel, skPrompt, "semantic-kernel", promptTemplateFactory);
await RunPromptAsync(kernel, handlebarsPrompt, "handlebars", promptTemplateFactory);
return RunPromptAsync(kernel, prompt, templateFormat, promptTemplateFactory);
}
public static async Task RunPromptAsync(Kernel kernel, string prompt, string templateFormat, IPromptTemplateFactory promptTemplateFactory)
private async Task RunPromptAsync(Kernel kernel, string prompt, string templateFormat, IPromptTemplateFactory promptTemplateFactory)
{
Console.WriteLine($"======== {templateFormat} : {prompt} ========");
WriteLine($"======== {templateFormat} : {prompt} ========");
var function = kernel.CreateFunctionFromPrompt(
promptConfig: new PromptTemplateConfig()
@@ -66,6 +58,10 @@ public static class Example64_MultiplePromptTemplates
};
var result = await kernel.InvokeAsync(function, arguments);
Console.WriteLine(result.GetValue<string>());
WriteLine(result.GetValue<string>());
}
public Example64_MultiplePromptTemplates(ITestOutputHelper output) : base(output)
{
}
}
@@ -8,40 +8,24 @@ using Microsoft.SemanticKernel.Planning.Handlebars;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using Plugins.DictionaryPlugin;
using RepoUtils;
using xRetry;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use the Handlebars sequential planner.
public static class Example65_HandlebarsPlanner
public class Example65_HandlebarsPlanner : BaseTest
{
private static int s_sampleIndex;
private const string CourseraPluginName = "CourseraPlugin";
/// <summary>
/// Show how to create a plan with Handlebars and execute it.
/// </summary>
public static async Task RunAsync()
private void WriteSampleHeading(string name)
{
s_sampleIndex = 1;
// Plugin with primitive types as inputs and outputs
await RunDictionaryWithBasicTypesSampleAsync();
await RunPoetrySampleAsync();
await RunBookSampleAsync();
await PlanNotPossibleSampleAsync();
// Plugin with Complex Types as inputs and outputs
await RunLocalDictionaryWithComplexTypesSampleAsync();
// OpenAPI plugin
await RunCourseraSampleAsync(shouldPrintPrompt: true);
WriteLine($"======== [Handlebars Planner] Sample {s_sampleIndex++} - Create and Execute Plan with: {name} ========");
}
private static void WriteSampleHeadingToConsole(string name)
{
Console.WriteLine($"======== [Handlebars Planner] Sample {s_sampleIndex++} - Create and Execute Plan with: {name} ========");
}
private static async Task RunSampleAsync(string goal, bool shouldPrintPrompt = false, params string[] pluginDirectoryNames)
private async Task RunSampleAsync(string goal, bool shouldPrintPrompt = false, params string[] pluginDirectoryNames)
{
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
@@ -50,7 +34,7 @@ public static class Example65_HandlebarsPlanner
if (apiKey == null || chatDeploymentName == null || chatModelId == null || endpoint == null)
{
Console.WriteLine("Azure endpoint, apiKey, deploymentName, or modelId not found. Skipping example.");
WriteLine("Azure endpoint, apiKey, deploymentName, or modelId not found. Skipping example.");
return;
}
@@ -101,7 +85,7 @@ public static class Example65_HandlebarsPlanner
AllowLoops = allowLoopsInPlan
});
Console.WriteLine($"Goal: {goal}");
WriteLine($"Goal: {goal}");
// Create the plan
var plan = await planner.CreatePlanAsync(kernel, goal);
@@ -109,19 +93,21 @@ public static class Example65_HandlebarsPlanner
// Print the prompt template
if (shouldPrintPrompt && plan.Prompt is not null)
{
Console.WriteLine($"\nPrompt template:\n{plan.Prompt}");
WriteLine($"\nPrompt template:\n{plan.Prompt}");
}
Console.WriteLine($"\nOriginal plan:\n{plan}");
WriteLine($"\nOriginal plan:\n{plan}");
// Execute the plan
var result = await plan.InvokeAsync(kernel);
Console.WriteLine($"\nResult:\n{result}\n");
WriteLine($"\nResult:\n{result}\n");
}
private static async Task PlanNotPossibleSampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(false)]
public async Task PlanNotPossibleSampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Plan Not Possible");
WriteSampleHeading("Plan Not Possible");
try
{
@@ -142,20 +128,25 @@ public static class Example65_HandlebarsPlanner
Therefore, I cannot create a Handlebars template to achieve the specified goal with the available helpers.
Additional helpers may be required.
*/
Console.WriteLine($"\n{ex.Message}\n");
WriteLine($"\n{ex.Message}\n");
}
}
private static async Task RunCourseraSampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(true)]
public Task RunCourseraSampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Coursera OpenAPI Plugin");
await RunSampleAsync("Show me courses about Artificial Intelligence.", shouldPrintPrompt, CourseraPluginName);
this.WriteSampleHeading("Coursera OpenAPI Plugin");
return RunSampleAsync("Show me courses about Artificial Intelligence.", shouldPrintPrompt, CourseraPluginName);
}
private static async Task RunDictionaryWithBasicTypesSampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(false)]
public Task RunDictionaryWithBasicTypesSampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Basic Types using Local Dictionary Plugin");
await RunSampleAsync("Get a random word and its definition.", shouldPrintPrompt, StringParamsDictionaryPlugin.PluginName);
this.WriteSampleHeading("Basic Types using Local Dictionary Plugin");
return RunSampleAsync("Get a random word and its definition.", shouldPrintPrompt, StringParamsDictionaryPlugin.PluginName);
/*
Original plan:
{{!-- Step 1: Get a random word --}}
@@ -172,10 +163,12 @@ public static class Example65_HandlebarsPlanner
*/
}
private static async Task RunLocalDictionaryWithComplexTypesSampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(true)]
public Task RunLocalDictionaryWithComplexTypesSampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Complex Types using Local Dictionary Plugin");
await RunSampleAsync("Teach me two random words and their definition.", shouldPrintPrompt, ComplexParamsDictionaryPlugin.PluginName);
this.WriteSampleHeading("Complex Types using Local Dictionary Plugin");
return RunSampleAsync("Teach me two random words and their definition.", shouldPrintPrompt, ComplexParamsDictionaryPlugin.PluginName);
/*
Original Plan:
{{!-- Step 1: Get two random dictionary entries --}}
@@ -206,10 +199,12 @@ public static class Example65_HandlebarsPlanner
*/
}
private static async Task RunPoetrySampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(false)]
public Task RunPoetrySampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Multiple Plugins");
await RunSampleAsync("Write a poem about John Doe, then translate it into Italian.", shouldPrintPrompt, "SummarizePlugin", "WriterPlugin");
this.WriteSampleHeading("Multiple Plugins");
return RunSampleAsync("Write a poem about John Doe, then translate it into Italian.", shouldPrintPrompt, "SummarizePlugin", "WriterPlugin");
/*
Original plan:
{{!-- Step 1: Initialize the scenario for the poem --}}
@@ -233,10 +228,12 @@ public static class Example65_HandlebarsPlanner
*/
}
private static async Task RunBookSampleAsync(bool shouldPrintPrompt = false)
[RetryTheory(typeof(HttpOperationException))]
[InlineData(false)]
public Task RunBookSampleAsync(bool shouldPrintPrompt = false)
{
WriteSampleHeadingToConsole("Loops and Conditionals");
await RunSampleAsync("Create a book with 3 chapters about a group of kids in a club called 'The Thinking Caps.'", shouldPrintPrompt, "WriterPlugin", "MiscPlugin");
this.WriteSampleHeading("Loops and Conditionals");
return RunSampleAsync("Create a book with 3 chapters about a group of kids in a club called 'The Thinking Caps.'", shouldPrintPrompt, "WriterPlugin", "MiscPlugin");
/*
Original plan:
{{!-- Step 1: Initialize the book title and chapter count --}}
@@ -260,4 +257,8 @@ public static class Example65_HandlebarsPlanner
{{/each}}
*/
}
public Example65_HandlebarsPlanner(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,18 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Planning;
using Microsoft.SemanticKernel.Plugins.Core;
using Plugins;
using Xunit;
using Xunit.Abstractions;
public static class Example66_FunctionCallingStepwisePlanner
namespace Examples;
public class Example66_FunctionCallingStepwisePlanner : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
string[] questions = new string[]
{
string[] questions = {
"What is the current hour number, plus 5?",
"What is 387 minus 22? Email the solution to John and Mary.",
"Write a limerick, translate it to Spanish, and send it to Jane",
@@ -30,7 +33,7 @@ public static class Example66_FunctionCallingStepwisePlanner
foreach (var question in questions)
{
FunctionCallingStepwisePlannerResult result = await planner.ExecuteAsync(kernel, question);
Console.WriteLine($"Q: {question}\nA: {result.FinalAnswer}");
WriteLine($"Q: {question}\nA: {result.FinalAnswer}");
// You can uncomment the line below to see the planner's process for completing the request.
// Console.WriteLine($"Chat history:\n{System.Text.Json.JsonSerializer.Serialize(result.ChatHistory)}");
@@ -44,11 +47,9 @@ public static class Example66_FunctionCallingStepwisePlanner
private static Kernel InitializeKernel()
{
Kernel kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName,
endpoint: TestConfiguration.AzureOpenAI.Endpoint,
apiKey: TestConfiguration.AzureOpenAI.ApiKey,
modelId: TestConfiguration.AzureOpenAI.ChatModelId)
.AddOpenAIChatCompletion(
apiKey: TestConfiguration.OpenAI.ApiKey,
modelId: "gpt-3.5-turbo-1106")
.Build();
kernel.ImportPluginFromType<EmailPlugin>();
@@ -57,4 +58,8 @@ public static class Example66_FunctionCallingStepwisePlanner
return kernel;
}
public Example66_FunctionCallingStepwisePlanner(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,17 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use multiple prompt template formats.
public static class Example67_KernelStreaming
public class Example67_KernelStreaming : BaseTest
{
/// <summary>
/// Show how to combine multiple prompt template factories.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
string apiKey = TestConfiguration.AzureOpenAI.ApiKey;
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName;
@@ -20,7 +24,7 @@ public static class Example67_KernelStreaming
if (apiKey == null || chatDeploymentName == null || chatModelId == null || endpoint == null)
{
Console.WriteLine("Azure endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
WriteLine("Azure endpoint, apiKey, deploymentName or modelId not found. Skipping example.");
return;
}
@@ -37,22 +41,32 @@ public static class Example67_KernelStreaming
var roleDisplayed = false;
Console.WriteLine("\n=== Prompt Function - Streaming ===\n");
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}");
WriteLine($"Role: {update.Role}");
fullContent += $"Role: {update.Role}\n";
roleDisplayed = true;
}
if (update.Content is { Length: > 0 })
{
Console.Write(update.Content);
fullContent += update.Content;
Write(update.Content);
}
}
WriteLine("\n------ Streamed Content ------\n");
WriteLine(fullContent);
}
public Example67_KernelStreaming(ITestOutputHelper output) : base(output)
{
}
}
@@ -4,11 +4,16 @@ using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to use GPT Vision model with different content types (text and image).
public static class Example68_GPTVision
public class Example68_GPTVision : BaseTest
{
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
const string ImageUri = "https://upload.wikimedia.org/wikipedia/commons/d/d5/Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg";
@@ -28,8 +33,10 @@ public static class Example68_GPTVision
var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
Console.WriteLine(reply.Content);
WriteLine(reply.Content);
}
Console.ReadKey();
public Example68_GPTVision(ITestOutputHelper output) : base(output)
{
}
}
@@ -5,14 +5,19 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// This example shows how to create a mutable <see cref="KernelPlugin"/>.
public static class Example69_MutableKernelPlugin
public class Example69_MutableKernelPlugin : BaseTest
{
/// <summary>
/// Show how to create a mutable <see cref="KernelPlugin"/>.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
var plugin = new MutableKernelPlugin("Plugin");
plugin.AddFunction(KernelFunctionFactory.CreateFromMethod(() => "Plugin.Function", "Function"));
@@ -22,7 +27,7 @@ public static class Example69_MutableKernelPlugin
var result = await kernel.InvokeAsync(kernel.Plugins["Plugin"]["Function"]);
Console.WriteLine($"Result: {result}");
WriteLine($"Result: {result}");
}
/// <summary>
@@ -73,4 +78,8 @@ public static class Example69_MutableKernelPlugin
/// <inheritdoc/>
public override IEnumerator<KernelFunction> GetEnumerator() => this._functions.Values.GetEnumerator();
}
public Example69_MutableKernelPlugin(ITestOutputHelper output) : base(output)
{
}
}
@@ -7,12 +7,16 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Experimental.Agents;
using Plugins;
using Resources;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// Showcase Open AI Agent integration with semantic kernel:
/// https://platform.openai.com/docs/api-reference/agents
/// </summary>
public static class Example70_Agent
public class Example70_Agent : BaseTest
{
/// <summary>
/// Specific model is required that supports agents and function calling.
@@ -20,42 +24,17 @@ public static class Example70_Agent
/// </summary>
private const string OpenAIFunctionEnabledModel = "gpt-3.5-turbo-1106";
/// <summary>
/// Show how to define an use a single agent using multiple patterns.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine("======== Example70_Agent ========");
if (TestConfiguration.OpenAI.ApiKey == null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
return;
}
// "Hello agent"
await RunSimpleChatAsync();
// Run agent with "method" tool/function
await RunWithMethodFunctionsAsync();
// Run agent with "prompt" tool/function
await RunWithPromptFunctionsAsync();
// Run agent as function
await RunAsFunctionAsync();
}
/// <summary>
/// Chat using the "Parrot" agent.
/// Tools/functions: None
/// </summary>
private static async Task RunSimpleChatAsync()
[Fact]
public Task RunSimpleChatAsync()
{
Console.WriteLine("======== Run:SimpleChat ========");
WriteLine("======== Run:SimpleChat ========");
// Call the common chat-loop
await ChatAsync(
return ChatAsync(
"Agents.ParrotAgent.yaml", // Defined under ./Resources/Agents
plugin: null, // No plugin
arguments: new KernelArguments { { "count", 3 } },
@@ -68,14 +47,15 @@ public static class Example70_Agent
/// Chat using the "Tool" agent and a method function.
/// Tools/functions: MenuPlugin
/// </summary>
private static async Task RunWithMethodFunctionsAsync()
[Fact]
public Task RunWithMethodFunctionsAsync()
{
Console.WriteLine("======== Run:WithMethodFunctions ========");
WriteLine("======== Run:WithMethodFunctions ========");
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
// Call the common chat-loop
await ChatAsync(
return ChatAsync(
"Agents.ToolAgent.yaml", // Defined under ./Resources/Agents
plugin,
arguments: null,
@@ -89,9 +69,10 @@ public static class Example70_Agent
/// Chat using the "Tool" agent and a prompt function.
/// Tools/functions: spellChecker prompt function
/// </summary>
private static async Task RunWithPromptFunctionsAsync()
[Fact]
public Task RunWithPromptFunctionsAsync()
{
Console.WriteLine("======== WithPromptFunctions ========");
WriteLine("======== WithPromptFunctions ========");
// Create a prompt function.
var function = KernelFunctionFactory.CreateFromPrompt(
@@ -102,7 +83,7 @@ public static class Example70_Agent
var plugin = KernelPluginFactory.CreateFromFunctions("spelling", "Spelling functions", new[] { function });
// Call the common chat-loop
await ChatAsync(
return ChatAsync(
"Agents.ToolAgent.yaml", // Defined under ./Resources/Agents
plugin,
arguments: null,
@@ -115,9 +96,10 @@ public static class Example70_Agent
/// <summary>
/// Invoke agent just like any other <see cref="KernelFunction"/>.
/// </summary>
private static async Task RunAsFunctionAsync()
[Fact]
public async Task RunAsFunctionAsync()
{
Console.WriteLine("======== Run:AsFunction ========");
WriteLine("======== Run:AsFunction ========");
// Create parrot agent, same as the other cases.
var agent =
@@ -132,7 +114,7 @@ public static class Example70_Agent
var response = await agent.AsPlugin().InvokeAsync("Practice makes perfect.", new KernelArguments { { "count", 2 } });
// Display result.
Console.WriteLine(response ?? $"No response from agent: {agent.Id}");
WriteLine(response ?? $"No response from agent: {agent.Id}");
}
finally
{
@@ -149,7 +131,7 @@ public static class Example70_Agent
/// 4. Create a chat-thread
/// 5. Process the provided "messages" on the chat-thread
/// </summary>
private static async Task ChatAsync(
private async Task ChatAsync(
string resourcePath,
KernelPlugin? plugin = null,
KernelArguments? arguments = null,
@@ -191,4 +173,8 @@ public static class Example70_Agent
agent.DeleteAsync());
}
}
public Example70_Agent(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -8,11 +7,15 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Experimental.Agents;
using Plugins;
using Resources;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
/// <summary>
/// Showcase complex Open AI Agent interactions using semantic kernel.
/// </summary>
public static class Example71_AgentDelegation
public class Example71_AgentDelegation : BaseTest
{
/// <summary>
/// Specific model is required that supports agents and function calling.
@@ -26,13 +29,14 @@ public static class Example71_AgentDelegation
/// <summary>
/// Show how to combine coordinate multiple agents.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
Console.WriteLine("======== Example71_AgentDelegation ========");
WriteLine("======== Example71_AgentDelegation ========");
if (TestConfiguration.OpenAI.ApiKey == null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
WriteLine("OpenAI apiKey not found. Skipping example.");
return;
}
@@ -78,8 +82,8 @@ public static class Example71_AgentDelegation
{
await foreach (var message in response)
{
Console.WriteLine($"[{message.Id}]");
Console.WriteLine($"# {message.Role}: {message.Content}");
WriteLine($"[{message.Id}]");
WriteLine($"# {message.Role}: {message.Content}");
}
}
}
@@ -98,4 +102,8 @@ public static class Example71_AgentDelegation
return agent;
}
public Example71_AgentDelegation(ITestOutputHelper output) : base(output)
{
}
}
@@ -5,12 +5,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Experimental.Agents;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// ReSharper disable once InconsistentNaming
/// <summary>
/// Showcase complex Open AI Agent collaboration using semantic kernel.
/// </summary>
public static class Example72_AgentCollaboration
public class Example72_AgentCollaboration : BaseTest
{
/// <summary>
/// Specific model is required that supports agents and function calling.
@@ -21,36 +24,14 @@ public static class Example72_AgentCollaboration
// Track agents for clean-up
private static readonly List<IAgent> s_agents = new();
/// <summary>
/// Show how to combine and coordinate multiple agents.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine("======== Example72_AgentCollaboration ========");
if (TestConfiguration.OpenAI.ApiKey == null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
return;
}
// NOTE: Either of these examples produce a conversation
// whose duration may vary depending on the collaboration dynamics.
// It is sometimes possible that agreement is never achieved.
// Explicit collaboration
await RunCollaborationAsync();
// Coordinate collaboration as plugin agents (equivalent to previous case - shared thread)
await RunAsPluginsAsync();
}
/// <summary>
/// Show how two agents are able to collaborate as agents on a single thread.
/// </summary>
private static async Task RunCollaborationAsync()
[Fact(Skip = "This test take more than 5 minutes to execute")]
public async Task RunCollaborationAsync()
{
Console.WriteLine("======== Run:Collaboration ========");
WriteLine("======== Example72_AgentCollaboration ========");
WriteLine("======== Run:Collaboration ========");
IAgentThread? thread = null;
try
{
@@ -99,9 +80,11 @@ public static class Example72_AgentCollaboration
/// While this may achieve an equivalent result to <see cref="RunCollaborationAsync"/>,
/// it is not using shared thread state for agent interaction.
/// </remarks>
private static async Task RunAsPluginsAsync()
[Fact(Skip = "This test take more than 2 minutes to execute")]
public async Task RunAsPluginsAsync()
{
Console.WriteLine("======== Run:AsPlugins ========");
WriteLine("======== Example72_AgentCollaboration ========");
WriteLine("======== Run:AsPlugins ========");
try
{
// Create copy-writer agent to generate ideas
@@ -123,7 +106,7 @@ public static class Example72_AgentCollaboration
var response = await coordinator.AsPlugin().InvokeAsync("concept: maps made out of egg cartons.");
// Display final result
Console.WriteLine(response);
WriteLine(response);
}
finally
{
@@ -132,7 +115,7 @@ public static class Example72_AgentCollaboration
}
}
private async static Task<IAgent> CreateCopyWriterAsync(IAgent? agent = null)
private static async Task<IAgent> CreateCopyWriterAsync(IAgent? agent = null)
{
return
Track(
@@ -157,7 +140,7 @@ public static class Example72_AgentCollaboration
.BuildAsync());
}
private static void DisplayMessages(IEnumerable<IChatMessage> messages, IAgent? agent = null)
private void DisplayMessages(IEnumerable<IChatMessage> messages, IAgent? agent = null)
{
foreach (var message in messages)
{
@@ -165,16 +148,16 @@ public static class Example72_AgentCollaboration
}
}
private static void DisplayMessage(IChatMessage message, IAgent? agent = null)
private void DisplayMessage(IChatMessage message, IAgent? agent = null)
{
Console.WriteLine($"[{message.Id}]");
WriteLine($"[{message.Id}]");
if (agent != null)
{
Console.WriteLine($"# {message.Role}: ({agent.Name}) {message.Content}");
WriteLine($"# {message.Role}: ({agent.Name}) {message.Content}");
}
else
{
Console.WriteLine($"# {message.Role}: {message.Content}");
WriteLine($"# {message.Role}: {message.Content}");
}
}
@@ -184,4 +167,8 @@ public static class Example72_AgentCollaboration
return agent;
}
public Example72_AgentCollaboration(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,16 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Experimental.Agents;
using Xunit;
using Xunit.Abstractions;
namespace Examples;
// ReSharper disable once InconsistentNaming
/// <summary>
/// Showcase hiearchical Open AI Agent interactions using semantic kernel.
/// </summary>
public static class Example73_AgentAuthoring
public class Example73_AgentAuthoring : BaseTest
{
/// <summary>
/// Specific model is required that supports agents and parallel function calling.
@@ -21,28 +23,10 @@ public static class Example73_AgentAuthoring
// Track agents for clean-up
private static readonly List<IAgent> s_agents = new();
/// <summary>
/// Show how to combine coordinate multiple agents.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine("======== Example73_AgentAuthoring ========");
if (TestConfiguration.OpenAI.ApiKey == null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
return;
}
// Run demo by invoking agent directly
await RunAgentAsync();
// Run demo by invoking agent as a plugin
await RunAsPluginAsync();
}
private static async Task RunAgentAsync()
[Fact(Skip = "This test take more than 2 minutes to execute")]
public async Task RunAgentAsync()
{
WriteLine("======== Example73_AgentAuthoring ========");
try
{
// Initialize the agent with tools
@@ -51,8 +35,8 @@ public static class Example73_AgentAuthoring
// "Stream" messages as they become available
await foreach (IChatMessage message in articleGenerator.InvokeAsync("Thai food is the best in the world"))
{
Console.WriteLine($"[{message.Id}]");
Console.WriteLine($"# {message.Role}: {message.Content}");
WriteLine($"[{message.Id}]");
WriteLine($"# {message.Role}: {message.Content}");
}
}
finally
@@ -61,8 +45,10 @@ public static class Example73_AgentAuthoring
}
}
private static async Task RunAsPluginAsync()
[Fact(Skip = "This test take more than 2 minutes to execute")]
public async Task RunAsPluginAsync()
{
WriteLine("======== Example73_AgentAuthoring ========");
try
{
// Initialize the agent with tools
@@ -72,7 +58,7 @@ public static class Example73_AgentAuthoring
string response = await articleGenerator.AsPlugin().InvokeAsync("Thai food is the best in the world");
// Display final result
Console.WriteLine(response);
WriteLine(response);
}
finally
{
@@ -113,7 +99,7 @@ public static class Example73_AgentAuthoring
.BuildAsync());
}
private async static Task<IAgent> CreateResearchGeneratorAsync()
private static async Task<IAgent> CreateResearchGeneratorAsync()
{
// Initialize agent so that it may be automatically deleted.
return
@@ -132,4 +118,8 @@ public static class Example73_AgentAuthoring
return agent;
}
public Example73_AgentAuthoring(ITestOutputHelper output) : base(output)
{
}
}
@@ -17,13 +17,13 @@ using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Plugins.Core;
using Microsoft.SemanticKernel.Plugins.Web;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
using NCalcPlugins;
using Xunit;
using Xunit.Abstractions;
/**
* This example shows how to use FlowOrchestrator to execute a given flow with interaction with client.
*/
// ReSharper disable once InconsistentNaming
public static class Example74_FlowOrchestrator
namespace Examples;
// This example shows how to use FlowOrchestrator to execute a given flow with interaction with client.
public class Example74_FlowOrchestrator : BaseTest
{
private static readonly Flow s_flow = FlowSerializer.DeserializeFromYaml(@"
name: FlowOrchestrator_Example_Flow
@@ -56,11 +56,9 @@ provides:
- email
");
public static Task RunAsync()
[Fact(Skip = "Can take more than 1 minute")]
public Task RunAsync()
{
// Load assemblies for external plugins
Console.WriteLine("Loading {0}", typeof(SimpleCalculatorPlugin).AssemblyQualifiedName);
return RunExampleAsync();
//return RunInteractiveAsync();
}
@@ -331,6 +329,10 @@ Do not expose the regex in your response.
public string? Content { get; set; }
}
}
public Example74_FlowOrchestrator(ITestOutputHelper output) : base(output)
{
}
}
//*****************************************************
@@ -1,17 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace GettingStarted;
// This example shows how to create and use a <see cref="Kernel"/>.
public static class Step1_Create_Kernel
public class Step1_Create_Kernel : BaseTest
{
/// <summary>
/// Show how to create a <see cref="Kernel"/> and use it to execute prompts.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
@@ -21,23 +26,30 @@ public static class Step1_Create_Kernel
.Build();
// Example 1. Invoke the kernel with a prompt and display the result
Console.WriteLine(await kernel.InvokePromptAsync("What color is the sky?"));
Console.WriteLine();
WriteLine(await kernel.InvokePromptAsync("What color is the sky?"));
WriteLine();
// Example 2. Invoke the kernel with a templated prompt and display the result
KernelArguments arguments = new() { { "topic", "sea" } };
Console.WriteLine(await kernel.InvokePromptAsync("What color is the {{$topic}}?", arguments));
Console.WriteLine();
WriteLine(await kernel.InvokePromptAsync("What color is the {{$topic}}?", arguments));
WriteLine();
// Example 3. Invoke the kernel with a templated prompt and stream the results to the display
await foreach (var update in kernel.InvokePromptStreamingAsync("What color is the {{$topic}}? Provide a detailed explanation.", arguments))
{
Console.Write(update);
Write(update);
// Console.Write(update);
}
Console.WriteLine();
WriteLine(string.Empty);
// Example 4. Invoke the kernel with a templated prompt and execution settings
arguments = new(new OpenAIPromptExecutionSettings { MaxTokens = 500, Temperature = 0.5 }) { { "topic", "dogs" } };
Console.WriteLine(await kernel.InvokePromptAsync("Tell me a story about {{$topic}}", arguments));
WriteLine(await kernel.InvokePromptAsync("Tell me a story about {{$topic}}", arguments));
}
public Step1_Create_Kernel(ITestOutputHelper output) : base(output)
{
}
}
@@ -3,16 +3,22 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
using Xunit.Abstractions;
namespace GettingStarted;
// This example shows how to load a <see cref="KernelPlugin"/> instances.
public static class Step2_Add_Plugins
public class Step2_Add_Plugins : BaseTest
{
/// <summary>
/// Shows different ways to load a <see cref="KernelPlugin"/> instances.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
@@ -23,14 +29,14 @@ public static class Step2_Add_Plugins
Kernel kernel = kernelBuilder.Build();
// Example 1. Invoke the kernel with a prompt that asks the AI for inromation it cannot provide and may hallucinate
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas?"));
WriteLine(await kernel.InvokePromptAsync("How many days until Christmas?"));
// Example 2. Invoke the kernel with a templated prompt that invokes a plugin and display the result
Console.WriteLine(await kernel.InvokePromptAsync("The current time is {{TimeInformation.GetCurrentUtcTime}}. How many days until Christmas?"));
WriteLine(await kernel.InvokePromptAsync("The current time is {{TimeInformation.GetCurrentUtcTime}}. How many days until Christmas?"));
// Example 3. Invoke the kernel with a prompt and allow the AI to automatically invoke functions
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
}
/// <summary>
@@ -42,4 +48,8 @@ public static class Step2_Add_Plugins
[Description("Retrieves the current time in UTC.")]
public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
}
public Step2_Add_Plugins(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,18 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Resources;
using Xunit;
using Xunit.Abstractions;
namespace GettingStarted;
// This example shows how to create a prompt <see cref="KernelFunction"/> from a YAML resource.
public static class Step3_Yaml_Prompt
public class Step3_Yaml_Prompt : BaseTest
{
/// <summary>
/// Show how to create a prompt <see cref="KernelFunction"/> from a YAML resource.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
@@ -26,7 +31,7 @@ public static class Step3_Yaml_Prompt
var function = kernel.CreateFunctionFromPromptYaml(generateStoryYaml);
// Invoke the prompt function and display the result
Console.WriteLine(await kernel.InvokeAsync(function, arguments: new()
WriteLine(await kernel.InvokeAsync(function, arguments: new()
{
{ "topic", "Dog" },
{ "length", "3" },
@@ -37,10 +42,14 @@ public static class Step3_Yaml_Prompt
function = kernel.CreateFunctionFromPromptYaml(generateStoryHandlebarsYaml, new HandlebarsPromptTemplateFactory());
// Invoke the prompt function and display the result
Console.WriteLine(await kernel.InvokeAsync(function, arguments: new()
WriteLine(await kernel.InvokeAsync(function, arguments: new()
{
{ "topic", "Cat" },
{ "length", "3" },
}));
}
public Step3_Yaml_Prompt(ITestOutputHelper output) : base(output)
{
}
}
@@ -3,18 +3,24 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Examples;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
namespace GettingStarted;
// This example shows how to using Dependency Injection with the Semantic Kernel
public static class Step4_Dependency_Injection
public class Step4_Dependency_Injection : BaseTest
{
/// <summary>
/// Show how to create a <see cref="Kernel"/> that participates in Dependency Injection.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// If an application follows DI guidelines, the following line is unnecessary because DI will inject an instance of the KernelClient class to a class that references it.
// DI container guidelines - https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#recommendations
@@ -25,7 +31,7 @@ public static class Step4_Dependency_Injection
KernelArguments arguments = new() { { "topic", "earth when viewed from space" } };
await foreach (var update in kernel.InvokePromptStreamingAsync("What color is the {{$topic}}? Provide a detailed explanation.", arguments))
{
Console.Write(update);
Write(update);
}
}
@@ -35,7 +41,7 @@ public static class Step4_Dependency_Injection
private static ServiceProvider BuildServiceProvider()
{
var collection = new ServiceCollection();
collection.AddSingleton<ILoggerFactory>(ConsoleLogger.LoggerFactory);
collection.AddSingleton(ConsoleLogger.LoggerFactory);
var kernelBuilder = collection.AddKernel();
kernelBuilder.Services.AddOpenAITextGeneration(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey);
@@ -65,4 +71,8 @@ public static class Step4_Dependency_Injection
return utcNow;
}
}
public Step4_Dependency_Injection(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,15 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
public static class Step5_Chat_Prompt
namespace GettingStarted;
public class Step5_Chat_Prompt : BaseTest
{
/// <summary>
/// Show how to construct a chat prompt and invoke it.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
@@ -23,6 +28,11 @@ public static class Step5_Chat_Prompt
<message role=""user"">What is Seattle?</message>
<message role=""system"">Respond with JSON.</message>
";
Console.WriteLine(await kernel.InvokePromptAsync(chatPrompt));
WriteLine(await kernel.InvokePromptAsync(chatPrompt));
}
public Step5_Chat_Prompt(ITestOutputHelper output) : base(output)
{
}
}
@@ -1,16 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
namespace GettingStarted;
// This example shows how to use rendering event hooks to ensure that prompts are rendered in a responsible manner.
public static class Step6_Responsible_AI
public class Step6_Responsible_AI : BaseTest
{
/// <summary>
/// Show how to use rendering event hooks to ensure that prompts are rendered in a responsible manner.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
@@ -33,7 +38,7 @@ public static class Step6_Responsible_AI
{
e.RenderedPrompt += " NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY";
Console.WriteLine(e.RenderedPrompt);
WriteLine(e.RenderedPrompt);
}
// Add the handlers to the kernel
@@ -41,6 +46,10 @@ public static class Step6_Responsible_AI
kernel.PromptRendered += MyRenderedHandler;
KernelArguments arguments = new() { { "card_number", "4444 3333 2222 1111" } };
Console.WriteLine(await kernel.InvokePromptAsync("Tell me some useful information about this credit card number {{$card_number}}?", arguments));
WriteLine(await kernel.InvokePromptAsync("Tell me some useful information about this credit card number {{$card_number}}?", arguments));
}
public Step6_Responsible_AI(ITestOutputHelper output) : base(output)
{
}
}
@@ -3,16 +3,22 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using RepoUtils;
using Xunit;
using Xunit.Abstractions;
public static class Step7_Observability
namespace GettingStarted;
public class Step7_Observability : BaseTest
{
/// <summary>
/// Shows different ways observe the execution of a <see cref="KernelPlugin"/> instances.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
@@ -25,13 +31,13 @@ public static class Step7_Observability
// Handler which is called before a function is invoked
void MyInvokingHandler(object? sender, FunctionInvokingEventArgs e)
{
Console.WriteLine($"Invoking {e.Function.Name}");
WriteLine($"Invoking {e.Function.Name}");
}
// Handler which is called after a prompt is rendered
void MyRenderedHandler(object? sender, PromptRenderedEventArgs e)
{
Console.WriteLine($"Prompt sent to model: {e.RenderedPrompt}");
WriteLine($"Prompt sent to model: {e.RenderedPrompt}");
}
// Handler which is called after a function is invoked
@@ -39,7 +45,7 @@ public static class Step7_Observability
{
if (e.Result.Metadata is not null && e.Result.Metadata.ContainsKey("Usage"))
{
Console.WriteLine($"Token usage: {e.Result.Metadata?["Usage"]?.AsJson()}");
WriteLine($"Token usage: {e.Result.Metadata?["Usage"]?.AsJson()}");
}
}
@@ -50,7 +56,7 @@ public static class Step7_Observability
// Invoke the kernel with a prompt and allow the AI to automatically invoke functions
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
}
/// <summary>
@@ -62,4 +68,8 @@ public static class Step7_Observability
[Description("Retrieves the current time in UTC.")]
public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
}
public Step7_Observability(ITestOutputHelper output) : base(output)
{
}
}
@@ -6,17 +6,23 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Examples;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Xunit;
using Xunit.Abstractions;
public static class Step8_Pipelining
namespace GettingStarted;
public class Step8_Pipelining : BaseTest
{
/// <summary>
/// Provides an example of combining multiple functions into a single function that invokes
/// them in a sequence, passing the output from one as input to the next.
/// </summary>
public static async Task RunAsync()
[Fact]
public async Task RunAsync()
{
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
@@ -25,7 +31,7 @@ public static class Step8_Pipelining
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Trace));
Kernel kernel = builder.Build();
Console.WriteLine("================ PIPELINE ================");
WriteLine("================ PIPELINE ================");
{
// Create a pipeline of functions that will parse a string into an int, multiply it by a double, truncate it to an int, and then humanize it.
KernelFunction parseInt32 = KernelFunctionFactory.CreateFromMethod((string s) => double.Parse(s, CultureInfo.InvariantCulture), "parseInt32");
@@ -47,10 +53,10 @@ public static class Step8_Pipelining
// - The parseInt32 function will be invoked, read "123.456" from the arguments, and parse it into (double)123.456.
// - The multiplyByN function will be invoked, with i=123.456 and n=78.90, and return (double)9740.6784.
// - The truncate function will be invoked, with d=9740.6784, and return (int)9740, which will be the final result.
Console.WriteLine(await pipeline.InvokeAsync(kernel, args));
WriteLine(await pipeline.InvokeAsync(kernel, args));
}
Console.WriteLine("================ GRAPH ================");
WriteLine("================ GRAPH ================");
{
KernelFunction rand = KernelFunctionFactory.CreateFromMethod(() => Random.Shared.Next(), "GetRandomInt32");
KernelFunction mult = KernelFunctionFactory.CreateFromMethod((int i, int j) => i * j, "Multiply");
@@ -65,9 +71,13 @@ public static class Step8_Pipelining
(mult, "")
}, "graph");
Console.WriteLine(await graph.InvokeAsync(kernel));
WriteLine(await graph.InvokeAsync(kernel));
}
}
public Step8_Pipelining(ITestOutputHelper output) : base(output)
{
}
}
public static class KernelFunctionCombinators
@@ -3,19 +3,17 @@
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>KernelSyntaxExamples</AssemblyName>
<RootNamespace></RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<RollForward>LatestMajor</RollForward>
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<!-- Suppress: "Declare types in namespaces", "Require ConfigureAwait", "Experimental" -->
<NoWarn>CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102</NoWarn>
<NoWarn>CS8618,IDE0009,CA1051,CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102</NoWarn>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Example12_SequentialPlanner.cs" />
<Compile Remove="Example28_ActionPlanner.cs" />
<Compile Remove="Example31_CustomPlanner.cs" />
<Compile Remove="Example51_StepwisePlanner.cs" />
<Compile Remove="RepoUtils\PlanExtensions.cs" />
</ItemGroup>
<ItemGroup>
@@ -24,6 +22,13 @@
<None Remove="Resources\travelinfo.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xRetry" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
@@ -36,7 +41,6 @@
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Polly" />
<PackageReference Include="SharpToken" />
<PackageReference Include="Microsoft.ML.Tokenizers" />
<PackageReference Include="Microsoft.DeepDev.TokenizerLib" />
@@ -69,7 +73,6 @@
<ProjectReference Include="..\..\src\Connectors\Connectors.Memory.Qdrant\Connectors.Memory.Qdrant.csproj" />
<ProjectReference Include="..\..\src\Plugins\Plugins.Web\Plugins.Web.csproj" />
<ProjectReference Include="..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
<ProjectReference Include="..\NCalcPlugins\NCalcPlugins.csproj" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
@@ -1,118 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Reliability;
using RepoUtils;
public static class Program
{
/// <summary>
/// We recommend using the Debug properties to set the filter as a command line argument.
/// If you don't want to use it, set the filter here.
/// Examples:
/// DefaultFilter = "18" => run only example 18 (also "180" if there is such test)
/// DefaultFilter = "chat" => run all examples with a name that contains "chat"
/// </summary>
public const string? DefaultFilter = "";
public static async Task Main(string[] args)
{
// Load configuration from environment variables or user secrets.
LoadUserSecrets();
// Execution canceled if the user presses Ctrl+C.
using CancellationTokenSource cancellationTokenSource = new();
CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken();
// Check if args[0] is provided
string? filter = args.Length > 0 ? args[0] : DefaultFilter;
// Run examples based on the filter
await RunExamplesAsync(filter, cancelToken);
}
private static async Task RunExamplesAsync(string? filter, CancellationToken cancellationToken)
{
var examples = (Assembly.GetExecutingAssembly().GetTypes())
.Select(type => type.Name).ToList();
// Filter and run examples
foreach (var example in examples)
{
if (string.IsNullOrEmpty(filter) || example.Contains(filter, StringComparison.OrdinalIgnoreCase))
{
try
{
var method = Assembly.GetExecutingAssembly().GetType(example)?.GetMethod("RunAsync");
if (method == null)
{
// Skip if the type does not have a RunAsync method
continue;
}
Console.WriteLine($"Running {example}...");
bool hasCancellationToken = method.GetParameters().Any(param => param.ParameterType == typeof(CancellationToken));
var taskParameters = hasCancellationToken ? new object[] { cancellationToken } : null;
if (method.Invoke(null, taskParameters) is Task t)
{
await t.SafeWaitAsync(cancellationToken);
}
else
{
method.Invoke(null, null);
}
}
catch (ConfigurationNotFoundException ex)
{
Console.WriteLine($"{ex.Message}. Skipping example {example}.");
}
}
}
}
private static void LoadUserSecrets()
{
IConfigurationRoot configRoot = new ConfigurationBuilder()
.AddJsonFile("appsettings.Development.json", true)
.AddEnvironmentVariables()
.AddUserSecrets<Env>()
.Build();
TestConfiguration.Initialize(configRoot);
}
private static CancellationToken ConsoleCancellationToken(this CancellationTokenSource tokenSource)
{
Console.CancelKeyPress += (s, e) =>
{
Console.WriteLine("Canceling...");
tokenSource.Cancel();
e.Cancel = true;
};
return tokenSource.Token;
}
private static async Task SafeWaitAsync(this Task task,
CancellationToken cancellationToken = default)
{
try
{
await task.WaitAsync(cancellationToken);
Console.WriteLine();
Console.WriteLine("== DONE ==");
}
catch (ConfigurationNotFoundException ex)
{
Console.WriteLine($"{ex.Message}. Skipping example.");
}
cancellationToken.ThrowIfCancellationRequested();
}
}
@@ -103,9 +103,6 @@ dotnet user-secrets set "Google:SearchEngineId" "..."
dotnet user-secrets set "Github:PAT" "github_pat_..."
dotnet user-secrets set "Apim:Endpoint" "https://apim...azure-api.net/"
dotnet user-secrets set "Apim:SubscriptionKey" "..."
dotnet user-secrets set "Postgres:ConnectionString" "..."
dotnet user-secrets set "Redis:Configuration" "..."
dotnet user-secrets set "Kusto:ConnectionString" "..."
@@ -173,10 +170,6 @@ Google__SearchEngineId
# Github
Github__PAT
# Azure API Management (APIM)
Apim__Endpoint
Apim__SubscriptionKey
# Other
Postgres__ConnectionString
Redis__Configuration
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.
using Xunit.Abstractions;
namespace Examples;
public static class TextOutputHelperExtensions
{
public static void WriteLine(this ITestOutputHelper testOutputHelper, object target)
{
testOutputHelper.WriteLine(target.ToString());
}
public static void WriteLine(this ITestOutputHelper testOutputHelper)
{
testOutputHelper.WriteLine(string.Empty);
}
public static void Write(this ITestOutputHelper testOutputHelper)
{
testOutputHelper.WriteLine(string.Empty);
}
/// <summary>
/// Current interface ITestOutputHelper does not have a Write method. This extension method adds it to make it analogous to Console.Write when used in Console apps.
/// </summary>
/// <param name="testOutputHelper">TestOutputHelper</param>
/// <param name="target">Target object to write</param>
public static void Write(this ITestOutputHelper testOutputHelper, object target)
{
testOutputHelper.WriteLine(target.ToString());
}
}
@@ -1,138 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using NCalc;
namespace NCalcPlugins;
/// <summary>
/// Plugin that enables the comprehension of mathematical problems presented in English / natural-language text, followed by the execution of the necessary calculations to solve those problems.
/// </summary>
public class LanguageCalculatorPlugin
{
private readonly KernelFunction _mathTranslator;
private const string MathTranslatorPrompt =
@"Translate a math problem into a expression that can be executed using .net NCalc library. Use the output of running this code to answer the question.
Available functions: Abs, Acos, Asin, Atan, Ceiling, Cos, Exp, Floor, IEEERemainder, Log, Log10, Max, Min, Pow, Round, Sign, Sin, Sqrt, Tan, and Truncate. in and if are also supported.
Question: $((Question with math problem.))
expression:``` $((single line mathematical expression that solves the problem))```
[Examples]
Question: What is 37593 * 67?
expression:```37593 * 67```
Question: what is 3 to the 2nd power?
expression:```Pow(3, 2)```
Question: what is sine of 0 radians?
expression:```Sin(0)```
Question: what is sine of 45 degrees?
expression:```Sin(45 * Pi /180 )```
Question: how many radians is 45 degrees?
expression:``` 45 * Pi / 180 ```
Question: what is the square root of 81?
expression:```Sqrt(81)```
Question: what is the angle whose sine is the number 1?
expression:```Asin(1)```
[End of Examples]
Question: {{ $input }}
";
/// <summary>
/// Initializes a new instance of the <see cref="LanguageCalculatorPlugin"/> class.
/// </summary>
public LanguageCalculatorPlugin()
{
this._mathTranslator = KernelFunctionFactory.CreateFromPrompt(
MathTranslatorPrompt,
functionName: "TranslateMathProblem",
description: "Used by 'Calculator' function.",
executionSettings: new PromptExecutionSettings()
{
ExtensionData = new Dictionary<string, object>()
{
{ "MaxTokens", 256 },
{ "Temperature", 0.0 },
{ "TopP", 1 },
}
});
}
/// <summary>
/// Calculates the result of a non-trivial math expression.
/// </summary>
/// <param name="input">A valid mathematical expression that could be executed by a calculator capable of more advanced math functions like sine/cosine/floor.</param>
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
[KernelFunction("Calculator"), Description("Useful for getting the result of a non-trivial math expression.")]
public async Task<string> CalculateAsync(
[Description("A valid mathematical expression that could be executed by a calculator capable of more advanced math functions like sin/cosine/floor.")]
string input,
Kernel kernel)
{
string answer;
try
{
var result = await kernel.InvokeAsync(this._mathTranslator, new() { ["input"] = input }).ConfigureAwait(false);
answer = result?.GetValue<string>() ?? string.Empty;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error in calculator for input {input} {ex.Message}", ex);
}
string pattern = @"```\s*(.*?)\s*```";
Match match = Regex.Match(answer, pattern, RegexOptions.Singleline);
if (match.Success)
{
var result = EvaluateMathExpression(match);
return result;
}
throw new InvalidOperationException($"Input value [{input}] could not be understood, received following {answer}");
}
private static string EvaluateMathExpression(Match match)
{
var textExpressions = match.Groups[1].Value;
var expr = new Expression(textExpressions, EvaluateOptions.IgnoreCase);
expr.EvaluateParameter += (string name, ParameterArgs args) =>
{
args.Result = name.ToLower(System.Globalization.CultureInfo.CurrentCulture) switch
{
"pi" => Math.PI,
"e" => Math.E,
_ => args.Result
};
};
try
{
if (expr.HasErrors())
{
return "Error:" + expr.Error + " could not evaluate " + textExpressions;
}
var result = expr.Evaluate();
return "Answer:" + result.ToString();
}
catch (Exception e)
{
throw new InvalidOperationException("could not evaluate " + textExpressions, e);
}
}
}
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreCLR-NCalc" />
</ItemGroup>
</Project>
@@ -1,36 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Microsoft.SemanticKernel;
namespace NCalcPlugins;
/// <summary>
/// Simple calculator plugin that evaluates a mathematical expression.
/// </summary>
public class SimpleCalculatorPlugin
{
private readonly KernelFunction _mathTranslator;
private static readonly string[] s_stopSequences = new[] { "Problem:", "Solution:" };
/// <summary>
/// Initializes a new instance of the <see cref="SimpleCalculatorPlugin"/> class.
/// </summary>
public SimpleCalculatorPlugin()
{
this._mathTranslator = KernelFunctionFactory.CreateFromPrompt(
"Task: Give the final solution for the problem. Be as concise as possible.\nProblem:4+4\nSolution:8\nProblem:{{$input}}\nSolution:\n",
functionName: "Calculator",
description: "Evaluate a mathematical expression. Input is a valid mathematical expression that could be executed by a simple calculator i.e. add, subtract, multiply and divide. Cannot use variables.",
executionSettings: new PromptExecutionSettings()
{
ExtensionData = new Dictionary<string, object>()
{
{ "MaxTokens", 256 },
{ "Temperature", 0.0 },
{ "StopSequences", s_stopSequences },
}
});
}
}
@@ -25,7 +25,6 @@
<ProjectReference Include="..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
<ProjectReference Include="..\..\src\Plugins\Plugins.Core\Plugins.Core.csproj" />
<ProjectReference Include="..\..\src\Plugins\Plugins.Web\Plugins.Web.csproj" />
<ProjectReference Include="..\NCalcPlugins\NCalcPlugins.csproj" />
</ItemGroup>
</Project>
@@ -2,7 +2,6 @@
using System;
using System.Net.Http;
using System.Runtime.CompilerServices;
using Azure;
using Azure.AI.OpenAI;
using Azure.Core;
@@ -98,16 +97,4 @@ internal sealed class AzureOpenAIClientCore : ClientCore
this.AddAttribute(DeploymentNameKey, deploymentName);
}
/// <summary>
/// Logs Azure OpenAI action details.
/// </summary>
/// <param name="callerMemberName">Caller member name. Populated automatically by runtime.</param>
internal void LogActionDetails([CallerMemberName] string? callerMemberName = default)
{
if (this.Logger.IsEnabled(LogLevel.Information))
{
this.Logger.LogInformation("Action: {Action}. Azure OpenAI Deployment Name: {DeploymentName}.", callerMemberName, this.DeploymentOrModelName);
}
}
}
@@ -30,7 +30,8 @@ public sealed class AzureOpenAIWithDataChatMessageContent : ChatMessageContent
: base(default, string.Empty, modelId, chatChoice, System.Text.Encoding.UTF8, CreateMetadataDictionary(metadata))
{
// An assistant message content must be present, otherwise the chat is not valid.
var chatMessage = chatChoice.Messages.First(m => string.Equals(m.Role, AuthorRole.Assistant.Label, StringComparison.OrdinalIgnoreCase));
var chatMessage = chatChoice.Messages.FirstOrDefault(m => string.Equals(m.Role, AuthorRole.Assistant.Label, StringComparison.OrdinalIgnoreCase)) ??
throw new ArgumentException("Chat is not valid. Chat message does not contain any messages with 'assistant' role.");
this.Content = chatMessage.Content;
this.Role = new AuthorRole(chatMessage.Role);
@@ -33,13 +33,13 @@ public static class OpenAIMemoryBuilderExtensions
string? modelId = null,
HttpClient? httpClient = null)
{
return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) =>
return builder.WithTextEmbeddingGeneration((loggerFactory, builderHttpClient) =>
new AzureOpenAITextEmbeddingGenerationService(
deploymentName,
endpoint,
apiKey,
modelId,
HttpClientProvider.GetHttpClient(httpClient),
HttpClientProvider.GetHttpClient(httpClient ?? builderHttpClient),
loggerFactory));
}
@@ -63,13 +63,13 @@ public static class OpenAIMemoryBuilderExtensions
string? modelId = null,
HttpClient? httpClient = null)
{
return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) =>
return builder.WithTextEmbeddingGeneration((loggerFactory, builderHttpClient) =>
new AzureOpenAITextEmbeddingGenerationService(
deploymentName,
endpoint,
credential,
modelId,
HttpClientProvider.GetHttpClient(httpClient),
HttpClientProvider.GetHttpClient(httpClient ?? builderHttpClient),
loggerFactory));
}
@@ -91,12 +91,12 @@ public static class OpenAIMemoryBuilderExtensions
string? orgId = null,
HttpClient? httpClient = null)
{
return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) =>
return builder.WithTextEmbeddingGeneration((loggerFactory, builderHttpClient) =>
new OpenAITextEmbeddingGenerationService(
modelId,
apiKey,
orgId,
HttpClientProvider.GetHttpClient(httpClient),
HttpClientProvider.GetHttpClient(httpClient ?? builderHttpClient),
loggerFactory));
}
}
@@ -93,7 +93,6 @@ public sealed class AzureOpenAITextEmbeddingGenerationService : ITextEmbeddingGe
Kernel? kernel = null,
CancellationToken cancellationToken = default)
{
this._core.LogActionDetails();
return this._core.GetEmbeddingsAsync(data, kernel, cancellationToken);
}
}
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Text.Json;
@@ -87,10 +86,7 @@ public sealed class OpenAITextToImageService : ITextToImageService
string format, Func<TextToImageResponse.Image, string> extractResponse,
CancellationToken cancellationToken)
{
Debug.Assert(width == height);
Debug.Assert(width is 256 or 512 or 1024);
Debug.Assert(format is "url" or "b64_json");
Debug.Assert(extractResponse is not null);
Verify.NotNull(extractResponse);
var requestBody = JsonSerializer.Serialize(new TextToImageRequest
{
@@ -27,14 +27,17 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Numerics.Tensors" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/AssertExtensions.cs"
Link="%(RecursiveDir)%(Filename)%(Extension)" />
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/AssertExtensions.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
@@ -55,16 +58,10 @@
</ItemGroup>
<ItemGroup>
<None Update="HuggingFace\TestData\completion_test_response.json">
<None Update="HuggingFace\TestData\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="HuggingFace\TestData\embeddings_test_response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="OpenAI\TestData\image_result_test_response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="OpenAI\TestData\image_generation_test_response.json">
<None Update="OpenAI\TestData\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace SemanticKernel.Connectors.UnitTests;
internal sealed class MultipleHttpMessageHandlerStub : DelegatingHandler
{
private int _callIteration = 0;
public List<HttpRequestHeaders?> RequestHeaders { get; private set; }
public List<HttpContentHeaders?> ContentHeaders { get; private set; }
public List<byte[]?> RequestContents { get; private set; }
public List<Uri?> RequestUris { get; private set; }
public List<HttpMethod?> Methods { get; private set; }
public List<HttpResponseMessage> ResponsesToReturn { get; set; }
public MultipleHttpMessageHandlerStub()
{
this.RequestHeaders = [];
this.ContentHeaders = [];
this.RequestContents = [];
this.RequestUris = [];
this.Methods = [];
this.ResponsesToReturn = [];
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this._callIteration++;
this.Methods.Add(request.Method);
this.RequestUris.Add(request.RequestUri);
this.RequestHeaders.Add(request.Headers);
this.ContentHeaders.Add(request.Content?.Headers);
var content = request.Content == null ? null : await request.Content.ReadAsByteArrayAsync(cancellationToken);
this.RequestContents.Add(content);
return await Task.FromResult(this.ResponsesToReturn[this._callIteration - 1]);
}
}
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="AzureOpenAIWithDataChatMessageContent"/> class.
/// </summary>
public sealed class AzureOpenAIWithDataChatMessageContentTests
{
[Fact]
public void ConstructorThrowsExceptionWhenAssistantMessageIsNotProvided()
{
// Arrange
var choice = new ChatWithDataChoice();
// Act & Assert
var exception = Assert.Throws<ArgumentException>(() => new AzureOpenAIWithDataChatMessageContent(choice, "model-id"));
Assert.Contains("Chat is not valid", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ConstructorReturnsInstanceWithNullToolContent()
{
// Arrange
var choice = new ChatWithDataChoice { Messages = [new() { Content = "Assistant content", Role = "assistant" }] };
// Act
var content = new AzureOpenAIWithDataChatMessageContent(choice, "model-id");
// Assert
Assert.Equal("Assistant content", content.Content);
Assert.Equal(AuthorRole.Assistant, content.Role);
Assert.Null(content.ToolContent);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorReturnsInstanceWithNonNullToolContent(bool includeMetadata)
{
// Arrange
var choice = new ChatWithDataChoice
{
Messages = [
new() { Content = "Assistant content", Role = "assistant" },
new() { Content = "Tool content", Role = "tool" }]
};
// Act
var content = includeMetadata ?
new AzureOpenAIWithDataChatMessageContent(choice, "model-id", new Dictionary<string, object?>()) :
new AzureOpenAIWithDataChatMessageContent(choice, "model-id");
// Assert
Assert.Equal("Assistant content", content.Content);
Assert.Equal("Tool content", content.ToolContent);
Assert.Equal(AuthorRole.Assistant, content.Role);
Assert.NotNull(content.Metadata);
Assert.Equal("Tool content", content.Metadata["ToolContent"]);
}
}
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="AzureOpenAIWithDataStreamingChatMessageContent"/> class.
/// </summary>
public sealed class AzureOpenAIWithDataStreamingChatMessageContentTests
{
[Theory]
[MemberData(nameof(ValidChoices))]
public void ConstructorWithValidChoiceSetsNonEmptyContent(object choice, string expectedContent)
{
// Arrange
var streamingChoice = choice as ChatWithDataStreamingChoice;
// Act
var content = new AzureOpenAIWithDataStreamingChatMessageContent(streamingChoice!, 0, "model-id");
// Assert
Assert.Equal(expectedContent, content.Content);
}
[Theory]
[MemberData(nameof(InvalidChoices))]
public void ConstructorWithInvalidChoiceSetsNullContent(object choice)
{
// Arrange
var streamingChoice = choice as ChatWithDataStreamingChoice;
// Act
var content = new AzureOpenAIWithDataStreamingChatMessageContent(streamingChoice!, 0, "model-id");
// Assert
Assert.Null(content.Content);
}
public static IEnumerable<object[]> ValidChoices
{
get
{
yield return new object[] { new ChatWithDataStreamingChoice { Messages = [new() { Delta = new() { Content = "Content 1" } }] }, "Content 1" };
yield return new object[] { new ChatWithDataStreamingChoice { Messages = [new() { Delta = new() { Content = "Content 2", Role = "Assistant" } }] }, "Content 2" };
}
}
public static IEnumerable<object[]> InvalidChoices
{
get
{
yield return new object[] { new ChatWithDataStreamingChoice { Messages = [new() { EndTurn = true }] } };
yield return new object[] { new ChatWithDataStreamingChoice { Messages = [new() { Delta = new() { Content = "Content", Role = "tool" } }] } };
}
}
}
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Azure.AI.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="OpenAIChatMessageContent"/> class.
/// </summary>
public sealed class OpenAIChatMessageContentTests
{
[Fact]
public void ConstructorsWorkCorrectly()
{
// Arrange
List<ChatCompletionsToolCall> toolCalls = [new FakeChatCompletionsToolCall("id")];
// Act
var content1 = new OpenAIChatMessageContent(new ChatRole("user"), "content1", "model-id1", toolCalls);
var content2 = new OpenAIChatMessageContent(AuthorRole.User, "content2", "model-id2", toolCalls);
// Assert
this.AssertChatMessageContent(AuthorRole.User, "content1", "model-id1", toolCalls, content1);
this.AssertChatMessageContent(AuthorRole.User, "content2", "model-id2", toolCalls, content2);
}
[Fact]
public void GetOpenAIFunctionToolCallsReturnsCorrectList()
{
// Arrange
List<ChatCompletionsToolCall> toolCalls = [
new ChatCompletionsFunctionToolCall("id1", "name", string.Empty),
new ChatCompletionsFunctionToolCall("id2", "name", string.Empty),
new FakeChatCompletionsToolCall("id3"),
new FakeChatCompletionsToolCall("id4")];
var content1 = new OpenAIChatMessageContent(AuthorRole.User, "content", "model-id", toolCalls);
var content2 = new OpenAIChatMessageContent(AuthorRole.User, "content", "model-id", []);
// Act
var actualToolCalls1 = content1.GetOpenAIFunctionToolCalls();
var actualToolCalls2 = content2.GetOpenAIFunctionToolCalls();
// Assert
Assert.Equal(2, actualToolCalls1.Count);
Assert.Equal("id1", actualToolCalls1[0].Id);
Assert.Equal("id2", actualToolCalls1[1].Id);
Assert.Empty(actualToolCalls2);
}
[Fact]
public void MetadataIsInitializedCorrectly()
{
// Arrange
var metadata = new Dictionary<string, object?> { { "key", "value" } };
List<ChatCompletionsToolCall> toolCalls = [
new ChatCompletionsFunctionToolCall("id1", "name", string.Empty),
new ChatCompletionsFunctionToolCall("id2", "name", string.Empty),
new FakeChatCompletionsToolCall("id3"),
new FakeChatCompletionsToolCall("id4")];
// Act
var content1 = new OpenAIChatMessageContent(AuthorRole.User, "content1", "model-id1", [], metadata);
var content2 = new OpenAIChatMessageContent(AuthorRole.User, "content2", "model-id2", toolCalls, metadata);
// Assert
Assert.NotNull(content1.Metadata);
Assert.Single(content1.Metadata);
Assert.NotNull(content2.Metadata);
Assert.Equal(2, content2.Metadata.Count);
Assert.Equal("value", content2.Metadata["key"]);
Assert.IsType<List<ChatCompletionsFunctionToolCall>>(content2.Metadata["ChatResponseMessage.FunctionToolCalls"]);
var actualToolCalls = content2.Metadata["ChatResponseMessage.FunctionToolCalls"] as List<ChatCompletionsFunctionToolCall>;
Assert.NotNull(actualToolCalls);
Assert.Equal(2, actualToolCalls.Count);
Assert.Equal("id1", actualToolCalls[0].Id);
Assert.Equal("id2", actualToolCalls[1].Id);
}
private void AssertChatMessageContent(
AuthorRole expectedRole,
string expectedContent,
string expectedModelId,
IReadOnlyList<ChatCompletionsToolCall> expectedToolCalls,
OpenAIChatMessageContent actualContent)
{
Assert.Equal(expectedRole, actualContent.Role);
Assert.Equal(expectedContent, actualContent.Content);
Assert.Equal(expectedModelId, actualContent.ModelId);
Assert.Same(expectedToolCalls, actualContent.ToolCalls);
}
private sealed class FakeChatCompletionsToolCall(string id) : ChatCompletionsToolCall(id)
{ }
}
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text;
using Azure.AI.OpenAI;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="OpenAIFunctionToolCall"/> class.
/// </summary>
public sealed class OpenAIFunctionToolCallTests
{
[Theory]
[InlineData("MyFunction", "MyFunction")]
[InlineData("MyPlugin_MyFunction", "MyPlugin_MyFunction")]
public void FullyQualifiedNameReturnsValidName(string toolCallName, string expectedName)
{
// Arrange
var toolCall = new ChatCompletionsFunctionToolCall("id", toolCallName, string.Empty);
var openAIFunctionToolCall = new OpenAIFunctionToolCall(toolCall);
// Act & Assert
Assert.Equal(expectedName, openAIFunctionToolCall.FullyQualifiedName);
}
[Fact]
public void ToStringReturnsCorrectValue()
{
// Arrange
var toolCall = new ChatCompletionsFunctionToolCall("id", "MyPlugin_MyFunction", "{\n \"location\": \"San Diego\",\n \"max_price\": 300\n}");
var openAIFunctionToolCall = new OpenAIFunctionToolCall(toolCall);
// Act & Assert
Assert.Equal("MyPlugin_MyFunction(location:San Diego, max_price:300)", openAIFunctionToolCall.ToString());
}
[Fact]
public void ConvertToolCallUpdatesWithEmptyIndexesReturnsEmptyToolCalls()
{
// Arrange
var toolCallIdsByIndex = new Dictionary<int, string>();
var functionNamesByIndex = new Dictionary<int, string>();
var functionArgumentBuildersByIndex = new Dictionary<int, StringBuilder>();
// Act
var toolCalls = OpenAIFunctionToolCall.ConvertToolCallUpdatesToChatCompletionsFunctionToolCalls(
ref toolCallIdsByIndex,
ref functionNamesByIndex,
ref functionArgumentBuildersByIndex);
// Assert
Assert.Empty(toolCalls);
}
[Fact]
public void ConvertToolCallUpdatesWithNotEmptyIndexesReturnsNotEmptyToolCalls()
{
// Arrange
var toolCallIdsByIndex = new Dictionary<int, string> { { 3, "test-id" } };
var functionNamesByIndex = new Dictionary<int, string> { { 3, "test-function" } };
var functionArgumentBuildersByIndex = new Dictionary<int, StringBuilder> { { 3, new("test-argument") } };
// Act
var toolCalls = OpenAIFunctionToolCall.ConvertToolCallUpdatesToChatCompletionsFunctionToolCalls(
ref toolCallIdsByIndex,
ref functionNamesByIndex,
ref functionArgumentBuildersByIndex);
// Assert
Assert.Single(toolCalls);
var toolCall = toolCalls[0];
Assert.Equal("test-id", toolCall.Id);
Assert.Equal("test-function", toolCall.Name);
Assert.Equal("test-argument", toolCall.Arguments);
}
}
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="OpenAIPluginCollectionExtensions"/> class.
/// </summary>
public sealed class OpenAIPluginCollectionExtensionsTests
{
[Fact]
public void TryGetFunctionAndArgumentsWithNonExistingFunctionReturnsFalse()
{
// Arrange
var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", []);
var plugins = new KernelPluginCollection([plugin]);
var toolCall = new ChatCompletionsFunctionToolCall("id", "MyPlugin_MyFunction", string.Empty);
// Act
var result = plugins.TryGetFunctionAndArguments(toolCall, out var actualFunction, out var actualArguments);
// Assert
Assert.False(result);
Assert.Null(actualFunction);
Assert.Null(actualArguments);
}
[Fact]
public void TryGetFunctionAndArgumentsWithoutArgumentsReturnsTrue()
{
// Arrange
var function = KernelFunctionFactory.CreateFromMethod(() => "Result", "MyFunction");
var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]);
var plugins = new KernelPluginCollection([plugin]);
var toolCall = new ChatCompletionsFunctionToolCall("id", "MyPlugin_MyFunction", string.Empty);
// Act
var result = plugins.TryGetFunctionAndArguments(toolCall, out var actualFunction, out var actualArguments);
// Assert
Assert.True(result);
Assert.Same(function, actualFunction);
Assert.Null(actualArguments);
}
[Fact]
public void TryGetFunctionAndArgumentsWithArgumentsReturnsTrue()
{
// Arrange
var function = KernelFunctionFactory.CreateFromMethod(() => "Result", "MyFunction");
var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]);
var plugins = new KernelPluginCollection([plugin]);
var toolCall = new ChatCompletionsFunctionToolCall("id", "MyPlugin_MyFunction", "{\n \"location\": \"San Diego\",\n \"max_price\": 300\n,\n \"null_argument\": null\n}");
// Act
var result = plugins.TryGetFunctionAndArguments(toolCall, out var actualFunction, out var actualArguments);
// Assert
Assert.True(result);
Assert.Same(function, actualFunction);
Assert.NotNull(actualArguments);
Assert.Equal("San Diego", actualArguments["location"]);
Assert.Equal("300", actualArguments["max_price"]);
Assert.Null(actualArguments["null_argument"]);
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="OpenAIStreamingTextContent"/> class.
/// </summary>
public sealed class OpenAIStreamingTextContentTests
{
[Fact]
public void ToByteArrayWorksCorrectly()
{
// Arrange
var expectedBytes = Encoding.UTF8.GetBytes("content");
var content = new OpenAIStreamingTextContent("content", 0, "model-id");
// Act
var actualBytes = content.ToByteArray();
// Assert
Assert.Equal(expectedBytes, actualBytes);
}
[Theory]
[InlineData(null, "")]
[InlineData("content", "content")]
public void ToStringWorksCorrectly(string? content, string expectedString)
{
// Arrange
var textContent = new OpenAIStreamingTextContent(content!, 0, "model-id");
// Act
var actualString = textContent.ToString();
// Assert
Assert.Equal(expectedString, actualString);
}
}
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Azure;
using Azure.Core;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.AzureSdk;
/// <summary>
/// Unit tests for <see cref="RequestFailedExceptionExtensions"/> class.
/// </summary>
public sealed class RequestFailedExceptionExtensionsTests
{
[Theory]
[InlineData(0, null)]
[InlineData(500, HttpStatusCode.InternalServerError)]
public void ToHttpOperationExceptionWithStatusReturnsValidException(int responseStatus, HttpStatusCode? httpStatusCode)
{
// Arrange
var exception = new RequestFailedException(responseStatus, "Error Message");
// Act
var actualException = exception.ToHttpOperationException();
// Assert
Assert.IsType<HttpOperationException>(actualException);
Assert.Equal(httpStatusCode, actualException.StatusCode);
Assert.Equal("Error Message", actualException.Message);
Assert.Same(exception, actualException.InnerException);
}
[Fact]
public void ToHttpOperationExceptionWithContentReturnsValidException()
{
// Arrange
using var response = new FakeResponse("Response Content", 500);
var exception = new RequestFailedException(response);
// Act
var actualException = exception.ToHttpOperationException();
// Assert
Assert.IsType<HttpOperationException>(actualException);
Assert.Equal(HttpStatusCode.InternalServerError, actualException.StatusCode);
Assert.Equal("Response Content", actualException.ResponseContent);
Assert.Same(exception, actualException.InnerException);
}
#region private
private sealed class FakeResponse(string responseContent, int status) : Response
{
private readonly string _responseContent = responseContent;
private readonly int _status = status;
private readonly IEnumerable<HttpHeader> _headers = new List<HttpHeader>();
public override BinaryData Content => BinaryData.FromString(this._responseContent);
public override int Status => this._status;
public override string ReasonPhrase => "Reason Phrase";
public override Stream? ContentStream { get => null; set => throw new NotImplementedException(); }
public override string ClientRequestId { get => "Client Request Id"; set => throw new NotImplementedException(); }
public override void Dispose() { }
protected override bool ContainsHeader(string name) => throw new NotImplementedException();
protected override IEnumerable<HttpHeader> EnumerateHeaders() => this._headers;
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
protected override bool TryGetHeader(string name, out string? value) => throw new NotImplementedException();
protected override bool TryGetHeaderValues(string name, out IEnumerable<string>? values) => throw new NotImplementedException();
#pragma warning restore CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
}
#endregion
}
@@ -0,0 +1,583 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using Azure.Core;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Moq;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.ChatCompletion;
/// <summary>
/// Unit tests for <see cref="AzureOpenAIChatCompletionService"/>
/// </summary>
public sealed class AzureOpenAIChatCompletionServiceTests : IDisposable
{
private readonly MultipleHttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly Mock<ILoggerFactory> _mockLoggerFactory;
public AzureOpenAIChatCompletionServiceTests()
{
this._messageHandlerStub = new MultipleHttpMessageHandlerStub();
this._httpClient = new HttpClient(this._messageHandlerStub, false);
this._mockLoggerFactory = new Mock<ILoggerFactory>();
var mockLogger = new Mock<ILogger>();
mockLogger.Setup(l => l.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
this._mockLoggerFactory.Setup(l => l.CreateLogger(It.IsAny<string>())).Returns(mockLogger.Object);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWithApiKeyWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var service = includeLoggerFactory ?
new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", loggerFactory: this._mockLoggerFactory.Object) :
new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id");
// Assert
Assert.NotNull(service);
Assert.Equal("model-id", service.Attributes["ModelId"]);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWithTokenCredentialWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var credentials = DelegatedTokenCredential.Create((_, _) => new AccessToken());
var service = includeLoggerFactory ?
new AzureOpenAIChatCompletionService("deployment", "https://endpoint", credentials, "model-id", loggerFactory: this._mockLoggerFactory.Object) :
new AzureOpenAIChatCompletionService("deployment", "https://endpoint", credentials, "model-id");
// Assert
Assert.NotNull(service);
Assert.Equal("model-id", service.Attributes["ModelId"]);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWithOpenAIClientWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var client = new OpenAIClient("key");
var service = includeLoggerFactory ?
new AzureOpenAIChatCompletionService("deployment", client, "model-id", loggerFactory: this._mockLoggerFactory.Object) :
new AzureOpenAIChatCompletionService("deployment", client, "model-id");
// Assert
Assert.NotNull(service);
Assert.Equal("model-id", service.Attributes["ModelId"]);
}
[Fact]
public async Task GetTextContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});
// Act
var result = await service.GetTextContentsAsync("Prompt");
// Assert
Assert.True(result.Count > 0);
Assert.Equal("Test chat response", result[0].Text);
var usage = result[0].Metadata?["Usage"] as CompletionsUsage;
Assert.NotNull(usage);
Assert.Equal(55, usage.PromptTokens);
Assert.Equal(100, usage.CompletionTokens);
Assert.Equal(155, usage.TotalTokens);
}
[Fact]
public async Task GetChatMessageContentsWithEmptyChoicesThrowsExceptionAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"id\":\"response-id\",\"object\":\"chat.completion\",\"created\":1704208954,\"model\":\"gpt-4\",\"choices\":[],\"usage\":{\"prompt_tokens\":55,\"completion_tokens\":100,\"total_tokens\":155},\"system_fingerprint\":null}")
});
// Act & Assert
var exception = await Assert.ThrowsAsync<KernelException>(() => service.GetChatMessageContentsAsync([]));
Assert.Equal("Chat completions not found", exception.Message);
}
[Theory]
[InlineData(0)]
[InlineData(129)]
public async Task GetChatMessageContentsWithInvalidResultsPerPromptValueThrowsExceptionAsync(int resultsPerPrompt)
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
var settings = new OpenAIPromptExecutionSettings { ResultsPerPrompt = resultsPerPrompt };
// Act & Assert
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => service.GetChatMessageContentsAsync([], settings));
Assert.Contains("The value must be in range between", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task GetChatMessageContentsHandlesSettingsCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
var settings = new OpenAIPromptExecutionSettings()
{
MaxTokens = 123,
Temperature = 0.6,
TopP = 0.5,
FrequencyPenalty = 1.6,
PresencePenalty = 1.2,
ResultsPerPrompt = 5,
Seed = 567,
TokenSelectionBiases = new Dictionary<int, int> { { 2, 3 } },
StopSequences = ["stop_sequence"]
};
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage("User Message");
chatHistory.AddSystemMessage("System Message");
chatHistory.AddAssistantMessage("Assistant Message");
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});
// Act
var result = await service.GetChatMessageContentsAsync(chatHistory, settings);
// Assert
var requestContent = this._messageHandlerStub.RequestContents[0];
Assert.NotNull(requestContent);
var content = JsonSerializer.Deserialize<JsonElement>(Encoding.UTF8.GetString(requestContent));
var messages = content.GetProperty("messages");
var userMessage = messages[0];
var systemMessage = messages[1];
var assistantMessage = messages[2];
Assert.Equal("user", userMessage.GetProperty("role").GetString());
Assert.Equal("User Message", userMessage.GetProperty("content").GetString());
Assert.Equal("system", systemMessage.GetProperty("role").GetString());
Assert.Equal("System Message", systemMessage.GetProperty("content").GetString());
Assert.Equal("assistant", assistantMessage.GetProperty("role").GetString());
Assert.Equal("Assistant Message", assistantMessage.GetProperty("content").GetString());
Assert.Equal(123, content.GetProperty("max_tokens").GetInt32());
Assert.Equal(0.6, content.GetProperty("temperature").GetDouble());
Assert.Equal(0.5, content.GetProperty("top_p").GetDouble());
Assert.Equal(1.6, content.GetProperty("frequency_penalty").GetDouble());
Assert.Equal(1.2, content.GetProperty("presence_penalty").GetDouble());
Assert.Equal(5, content.GetProperty("n").GetInt32());
Assert.Equal(567, content.GetProperty("seed").GetInt32());
Assert.Equal(3, content.GetProperty("logit_bias").GetProperty("2").GetInt32());
Assert.Equal("stop_sequence", content.GetProperty("stop")[0].GetString());
}
[Theory]
[MemberData(nameof(ResponseFormats))]
public async Task GetChatMessageContentsHandlesResponseFormatCorrectlyAsync(object responseFormat, string? expectedResponseType)
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
var settings = new OpenAIPromptExecutionSettings
{
ResponseFormat = responseFormat
};
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});
// Act
var result = await service.GetChatMessageContentsAsync([], settings);
// Assert
var requestContent = this._messageHandlerStub.RequestContents[0];
Assert.NotNull(requestContent);
var content = JsonSerializer.Deserialize<JsonElement>(Encoding.UTF8.GetString(requestContent));
Assert.Equal(expectedResponseType, content.GetProperty("response_format").GetProperty("type").GetString());
}
[Theory]
[MemberData(nameof(ToolCallBehaviors))]
public async Task GetChatMessageContentsWorksCorrectlyAsync(ToolCallBehavior behavior)
{
// Arrange
var kernel = Kernel.CreateBuilder().Build();
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = behavior };
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});
// Act
var result = await service.GetChatMessageContentsAsync([], settings, kernel);
// Assert
Assert.True(result.Count > 0);
Assert.Equal("Test chat response", result[0].Content);
var usage = result[0].Metadata?["Usage"] as CompletionsUsage;
Assert.NotNull(usage);
Assert.Equal(55, usage.PromptTokens);
Assert.Equal(100, usage.CompletionTokens);
Assert.Equal(155, usage.TotalTokens);
}
[Fact]
public async Task GetChatMessageContentsWithFunctionCallAsync()
{
// Arrange
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function1 = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
var function2 = KernelFunctionFactory.CreateFromMethod((string argument) =>
{
functionCallCount++;
throw new ArgumentException("Some exception");
}, "FunctionWithException");
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("MyPlugin", [function1, function2]));
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
using var response1 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_multiple_function_calls_test_response.json")) };
using var response2 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json")) };
this._messageHandlerStub.ResponsesToReturn = [response1, response2];
// Act
var result = await service.GetChatMessageContentsAsync([], settings, kernel);
// Assert
Assert.True(result.Count > 0);
Assert.Equal("Test chat response", result[0].Content);
Assert.Equal(2, functionCallCount);
}
[Fact]
public async Task GetChatMessageContentsWithFunctionCallMaximumAutoInvokeAttemptsAsync()
{
// Arrange
const int DefaultMaximumAutoInvokeAttempts = 5;
const int AutoInvokeResponsesCount = 6;
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]));
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
var responses = new List<HttpResponseMessage>();
for (var i = 0; i < AutoInvokeResponsesCount; i++)
{
responses.Add(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_single_function_call_test_response.json")) });
}
this._messageHandlerStub.ResponsesToReturn = responses;
// Act
var result = await service.GetChatMessageContentsAsync([], settings, kernel);
// Assert
Assert.Equal(DefaultMaximumAutoInvokeAttempts, functionCallCount);
}
[Fact]
public async Task GetChatMessageContentsWithRequiredFunctionCallAsync()
{
// Arrange
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]);
var openAIFunction = plugin.GetFunctionsMetadata().First().ToOpenAIFunction();
kernel.Plugins.Add(plugin);
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.RequireFunction(openAIFunction, autoInvoke: true) };
using var response1 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_single_function_call_test_response.json")) };
using var response2 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json")) };
this._messageHandlerStub.ResponsesToReturn = [response1, response2];
// Act
var result = await service.GetChatMessageContentsAsync([], settings, kernel);
// Assert
Assert.Equal(1, functionCallCount);
var requestContents = this._messageHandlerStub.RequestContents;
Assert.Equal(2, requestContents.Count);
requestContents.ForEach(Assert.NotNull);
var firstContent = Encoding.UTF8.GetString(requestContents[0]!);
var secondContent = Encoding.UTF8.GetString(requestContents[1]!);
var firstContentJson = JsonSerializer.Deserialize<JsonElement>(firstContent);
var secondContentJson = JsonSerializer.Deserialize<JsonElement>(secondContent);
Assert.Equal(1, firstContentJson.GetProperty("tools").GetArrayLength());
Assert.Equal("MyPlugin_GetCurrentWeather", firstContentJson.GetProperty("tool_choice").GetProperty("function").GetProperty("name").GetString());
Assert.Equal("none", secondContentJson.GetProperty("tool_choice").GetString());
}
[Fact]
public async Task GetStreamingTextContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")));
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
});
// Act & Assert
await foreach (var chunk in service.GetStreamingTextContentsAsync("Prompt"))
{
Assert.Equal("Test chat streaming response", chunk.Text);
}
}
[Fact]
public async Task GetStreamingChatMessageContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")));
this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
});
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([]))
{
Assert.Equal("Test chat streaming response", chunk.Content);
}
}
[Fact]
public async Task GetStreamingChatMessageContentsWithFunctionCallAsync()
{
// Arrange
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function1 = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
var function2 = KernelFunctionFactory.CreateFromMethod((string argument) =>
{
functionCallCount++;
throw new ArgumentException("Some exception");
}, "FunctionWithException");
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("MyPlugin", [function1, function2]));
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
using var response1 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_streaming_multiple_function_calls_test_response.txt")) };
using var response2 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")) };
this._messageHandlerStub.ResponsesToReturn = [response1, response2];
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([], settings, kernel))
{
Assert.Equal("Test chat streaming response", chunk.Content);
}
Assert.Equal(2, functionCallCount);
}
[Fact]
public async Task GetStreamingChatMessageContentsWithFunctionCallMaximumAutoInvokeAttemptsAsync()
{
// Arrange
const int DefaultMaximumAutoInvokeAttempts = 5;
const int AutoInvokeResponsesCount = 6;
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]));
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
var responses = new List<HttpResponseMessage>();
for (var i = 0; i < AutoInvokeResponsesCount; i++)
{
responses.Add(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_streaming_single_function_call_test_response.txt")) });
}
this._messageHandlerStub.ResponsesToReturn = responses;
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([], settings, kernel))
{
Assert.Equal("Test chat streaming response", chunk.Content);
}
Assert.Equal(DefaultMaximumAutoInvokeAttempts, functionCallCount);
}
[Fact]
public async Task GetStreamingChatMessageContentsWithRequiredFunctionCallAsync()
{
// Arrange
int functionCallCount = 0;
var kernel = Kernel.CreateBuilder().Build();
var function = KernelFunctionFactory.CreateFromMethod((string location) =>
{
functionCallCount++;
return "Some weather";
}, "GetCurrentWeather");
var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]);
var openAIFunction = plugin.GetFunctionsMetadata().First().ToOpenAIFunction();
kernel.Plugins.Add(plugin);
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient, this._mockLoggerFactory.Object);
var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.RequireFunction(openAIFunction, autoInvoke: true) };
using var response1 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_streaming_single_function_call_test_response.txt")) };
using var response2 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")) };
this._messageHandlerStub.ResponsesToReturn = [response1, response2];
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([], settings, kernel))
{
Assert.Equal("Test chat streaming response", chunk.Content);
}
Assert.Equal(1, functionCallCount);
var requestContents = this._messageHandlerStub.RequestContents;
Assert.Equal(2, requestContents.Count);
requestContents.ForEach(Assert.NotNull);
var firstContent = Encoding.UTF8.GetString(requestContents[0]!);
var secondContent = Encoding.UTF8.GetString(requestContents[1]!);
var firstContentJson = JsonSerializer.Deserialize<JsonElement>(firstContent);
var secondContentJson = JsonSerializer.Deserialize<JsonElement>(secondContent);
Assert.Equal(1, firstContentJson.GetProperty("tools").GetArrayLength());
Assert.Equal("MyPlugin_GetCurrentWeather", firstContentJson.GetProperty("tool_choice").GetProperty("function").GetProperty("name").GetString());
Assert.Equal("none", secondContentJson.GetProperty("tool_choice").GetString());
}
public void Dispose()
{
this._httpClient.Dispose();
this._messageHandlerStub.Dispose();
}
public static TheoryData<ToolCallBehavior> ToolCallBehaviors => new()
{
ToolCallBehavior.EnableKernelFunctions,
ToolCallBehavior.AutoInvokeKernelFunctions
};
public static TheoryData<object, string?> ResponseFormats => new()
{
{ new FakeChatCompletionsResponseFormat(), null },
{ "json_object", "json_object" },
{ "text", "text" }
};
private sealed class FakeChatCompletionsResponseFormat : ChatCompletionsResponseFormat { }
}
@@ -3,14 +3,19 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.TextGeneration;
using Moq;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.ChatCompletion;
@@ -18,17 +23,19 @@ namespace SemanticKernel.Connectors.UnitTests.OpenAI.ChatCompletion;
/// <summary>
/// Unit tests for <see cref="OpenAIChatCompletionService"/>
/// </summary>
public sealed class OpenAIChatCompletionTests : IDisposable
public sealed class OpenAIChatCompletionServiceTests : IDisposable
{
private readonly HttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly OpenAIFunction _timepluginDate, _timepluginNow;
private readonly OpenAIPromptExecutionSettings _executionSettings;
private readonly Mock<ILoggerFactory> _mockLoggerFactory;
public OpenAIChatCompletionTests()
public OpenAIChatCompletionServiceTests()
{
this._messageHandlerStub = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._messageHandlerStub, false);
this._mockLoggerFactory = new Mock<ILoggerFactory>();
IList<KernelFunctionMetadata> functions = KernelPluginFactory.CreateFromFunctions("TimePlugin", new[]
{
@@ -45,6 +52,37 @@ public sealed class OpenAIChatCompletionTests : IDisposable
};
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWithApiKeyWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var service = includeLoggerFactory ?
new OpenAIChatCompletionService("model-id", "api-key", "organization", loggerFactory: this._mockLoggerFactory.Object) :
new OpenAIChatCompletionService("model-id", "api-key", "organization");
// Assert
Assert.NotNull(service);
Assert.Equal("model-id", service.Attributes["ModelId"]);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWithOpenAIClientWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var client = new OpenAIClient("key");
var service = includeLoggerFactory ?
new OpenAIChatCompletionService("model-id", client, loggerFactory: this._mockLoggerFactory.Object) :
new OpenAIChatCompletionService("model-id", client);
// Assert
Assert.NotNull(service);
Assert.Equal("model-id", service.Attributes["ModelId"]);
}
[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingAutoAsync()
{
@@ -163,6 +201,44 @@ public sealed class OpenAIChatCompletionTests : IDisposable
Assert.Equal("gpt-3.5-turbo", textContent.ModelId);
}
[Fact]
public async Task GetStreamingTextContentsWorksCorrectlyAsync()
{
// Arrange
var service = new OpenAIChatCompletionService("model-id", "api-key", "organization", this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")));
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
// Act & Assert
await foreach (var chunk in service.GetStreamingTextContentsAsync("Prompt"))
{
Assert.Equal("Test chat streaming response", chunk.Text);
}
}
[Fact]
public async Task GetStreamingChatMessageContentsWorksCorrectlyAsync()
{
// Arrange
var service = new OpenAIChatCompletionService("model-id", "api-key", "organization", this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_streaming_test_response.txt")));
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([]))
{
Assert.Equal("Test chat streaming response", chunk.Content);
}
}
[Fact]
public async Task ItAddsSystemMessageAsync()
{
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Moq;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI.ChatCompletionWithData;
@@ -19,6 +22,7 @@ public sealed class AzureOpenAIChatCompletionWithDataTests : IDisposable
private readonly HttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly Mock<ILoggerFactory> _mockLoggerFactory;
public AzureOpenAIChatCompletionWithDataTests()
{
@@ -26,6 +30,22 @@ public sealed class AzureOpenAIChatCompletionWithDataTests : IDisposable
this._messageHandlerStub = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._messageHandlerStub, false);
this._mockLoggerFactory = new Mock<ILoggerFactory>();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ConstructorWorksCorrectly(bool includeLoggerFactory)
{
// Arrange & Act
var service = includeLoggerFactory ?
new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient, this._mockLoggerFactory.Object) :
new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
// Assert
Assert.NotNull(service);
Assert.Equal("fake-completion-model-id", service.Attributes["ModelId"]);
}
[Fact]
@@ -33,10 +53,10 @@ public sealed class AzureOpenAIChatCompletionWithDataTests : IDisposable
{
// Arrange
const string ExpectedUri = "https://fake-completion-endpoint/openai/deployments/fake-completion-model-id/extensions/chat/completions?api-version=fake-api-version";
var chatCompletion = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
var service = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
// Act
await chatCompletion.GetChatMessageContentsAsync(new ChatHistory());
await service.GetChatMessageContentsAsync([]);
// Assert
var actualUri = this._messageHandlerStub.RequestUri?.AbsoluteUri;
@@ -58,10 +78,10 @@ public sealed class AzureOpenAIChatCompletionWithDataTests : IDisposable
var config = this.GetConfig();
config.CompletionApiVersion = string.Empty;
var chatCompletion = new AzureOpenAIChatCompletionWithDataService(config, this._httpClient);
var service = new AzureOpenAIChatCompletionWithDataService(config, this._httpClient);
// Act
await chatCompletion.GetChatMessageContentsAsync(new ChatHistory());
await service.GetChatMessageContentsAsync([]);
// Assert
var actualUri = this._messageHandlerStub.RequestUri?.AbsoluteUri;
@@ -69,6 +89,94 @@ public sealed class AzureOpenAIChatCompletionWithDataTests : IDisposable
Assert.Contains("2023-06-01-preview", actualUri, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task GetChatMessageContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_with_data_test_response.json"))
};
// Act
var result = await service.GetChatMessageContentsAsync([]);
// Assert
Assert.True(result.Count > 0);
Assert.Equal("Test chat with data response", result[0].Content);
var usage = result[0].Metadata?["Usage"] as ChatWithDataUsage;
Assert.NotNull(usage);
Assert.Equal(55, usage.PromptTokens);
Assert.Equal(100, usage.CompletionTokens);
Assert.Equal(155, usage.TotalTokens);
}
[Fact]
public async Task GetStreamingChatMessageContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_with_data_streaming_test_response.txt")));
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
// Act & Assert
await foreach (var chunk in service.GetStreamingChatMessageContentsAsync([]))
{
Assert.Equal("Test chat with data streaming response", chunk.Content);
}
}
[Fact]
public async Task GetTextContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_with_data_test_response.json"))
};
// Act
var result = await service.GetTextContentsAsync("Prompt");
// Assert
Assert.True(result.Count > 0);
Assert.Equal("Test chat with data response", result[0].Text);
var usage = result[0].Metadata?["Usage"] as ChatWithDataUsage;
Assert.NotNull(usage);
Assert.Equal(55, usage.PromptTokens);
Assert.Equal(100, usage.CompletionTokens);
Assert.Equal(155, usage.TotalTokens);
}
[Fact]
public async Task GetStreamingTextContentsWorksCorrectlyAsync()
{
// Arrange
var service = new AzureOpenAIChatCompletionWithDataService(this._config, this._httpClient);
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(OpenAITestHelper.GetTestResponse("chat_completion_with_data_streaming_test_response.txt")));
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
// Act & Assert
await foreach (var chunk in service.GetStreamingTextContentsAsync("Prompt"))
{
Assert.Equal("Test chat with data streaming response", chunk.Text);
}
}
public void Dispose()
{
this._httpClient.Dispose();
@@ -13,6 +13,38 @@ namespace SemanticKernel.Connectors.UnitTests.OpenAI.FunctionCalling;
public sealed class OpenAIFunctionTests
{
[Theory]
[InlineData(null, null, "", "")]
[InlineData("name", "description", "name", "description")]
public void ItInitializesOpenAIFunctionParameterCorrectly(string? name, string? description, string expectedName, string expectedDescription)
{
// Arrange & Act
var schema = KernelJsonSchema.Parse("{\"type\": \"object\" }");
var functionParameter = new OpenAIFunctionParameter(name, description, true, typeof(string), schema);
// Assert
Assert.Equal(expectedName, functionParameter.Name);
Assert.Equal(expectedDescription, functionParameter.Description);
Assert.True(functionParameter.IsRequired);
Assert.Equal(typeof(string), functionParameter.ParameterType);
Assert.Same(schema, functionParameter.Schema);
}
[Theory]
[InlineData(null, "")]
[InlineData("description", "description")]
public void ItInitializesOpenAIFunctionReturnParameterCorrectly(string? description, string expectedDescription)
{
// Arrange & Act
var schema = KernelJsonSchema.Parse("{\"type\": \"object\" }");
var functionParameter = new OpenAIFunctionReturnParameter(description, typeof(string), schema);
// Assert
Assert.Equal(expectedDescription, functionParameter.Description);
Assert.Equal(typeof(string), functionParameter.ParameterType);
Assert.Same(schema, functionParameter.Schema);
}
[Fact]
public void ItCanConvertToFunctionDefinitionWithNoPluginName()
{
@@ -27,6 +59,19 @@ public sealed class OpenAIFunctionTests
Assert.Equal(sut.Description, result.Description);
}
[Fact]
public void ItCanConvertToFunctionDefinitionWithNullParameters()
{
// Arrange
OpenAIFunction sut = new("plugin", "function", "description", null, null);
// Act
var result = sut.ToFunctionDefinition();
// Assert
Assert.Equal("{\"type\":\"object\",\"required\":[],\"properties\":{}}", result.Parameters.ToString());
}
[Fact]
public void ItCanConvertToFunctionDefinitionWithPluginName()
{
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.Core;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Memory;
using Moq;
using Xunit;
namespace SemanticKernel.Connectors.UnitTests.OpenAI;
/// <summary>
/// Unit tests for <see cref="OpenAIMemoryBuilderExtensions"/> class.
/// </summary>
public sealed class OpenAIMemoryBuilderExtensionsTests
{
private readonly Mock<IMemoryStore> _mockMemoryStore = new();
[Fact]
public void AzureOpenAITextEmbeddingGenerationWithApiKeyWorksCorrectly()
{
// Arrange
var builder = new MemoryBuilder();
// Act
var memory = builder
.WithAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", "api-key", "model-id")
.WithMemoryStore(this._mockMemoryStore.Object)
.Build();
// Assert
Assert.NotNull(memory);
}
[Fact]
public void AzureOpenAITextEmbeddingGenerationWithTokenCredentialWorksCorrectly()
{
// Arrange
var builder = new MemoryBuilder();
var credentials = DelegatedTokenCredential.Create((_, _) => new AccessToken());
// Act
var memory = builder
.WithAzureOpenAITextEmbeddingGeneration("deployment-name", "https://endpoint", credentials, "model-id")
.WithMemoryStore(this._mockMemoryStore.Object)
.Build();
// Assert
Assert.NotNull(memory);
}
[Fact]
public void OpenAITextEmbeddingGenerationWithApiKeyWorksCorrectly()
{
// Arrange
var builder = new MemoryBuilder();
// Act
var memory = builder
.WithOpenAITextEmbeddingGeneration("model-id", "api-key", "organization-id")
.WithMemoryStore(this._mockMemoryStore.Object)
.Build();
// Assert
Assert.NotNull(memory);
}
}

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