chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>GettingStarted</AssemblyName>
<RootNamespace></RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net10.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<!-- Suppress: "Declare types in namespaces", "Require ConfigureAwait", "Experimental" -->
<NoWarn>$(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0101</NoWarn>
<OutputType>Library</OutputType>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\GenerateStory.yaml" />
<None Remove="Resources\GenerateStoryHandlebars.yaml" />
<None Remove="Resources\repair-service.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\repair-service.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\GenerateStory.yaml" />
<EmbeddedResource Include="Resources\GenerateStoryHandlebars.yaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xRetry" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.abstractions" />
<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.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.ML.Tokenizers" />
<PackageReference Include="System.Numerics.Tensors" />
</ItemGroup>
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/samples/SamplesInternalUtilities.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj" />
<ProjectReference Include="..\..\src\Extensions\PromptTemplates.Handlebars\PromptTemplates.Handlebars.csproj" />
<ProjectReference Include="..\..\src\Functions\Functions.OpenApi\Functions.OpenApi.csproj" />
<ProjectReference Include="..\..\src\Functions\Functions.Yaml\Functions.Yaml.csproj" />
<ProjectReference Include="..\..\src\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" />
<ProjectReference Include="..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<Using Include="Xunit.Abstractions" />
</ItemGroup>
</Project>
+37
View File
@@ -0,0 +1,37 @@
# Starting With Semantic Kernel
This project contains a step by step guide to get started with the Semantic Kernel.
The examples can be run as integration tests but their code can also be copied to stand-alone programs.
## Configuring Secrets
Most of the examples will require secrets and credentials, to access OpenAI, Azure OpenAI,
Bing and other resources. We suggest using .NET
[Secret Manager](https://learn.microsoft.com/aspnet/core/security/app-secrets)
to avoid the risk of leaking secrets into the repository, branches and pull requests.
You can also use environment variables if you prefer.
To set your secrets with Secret Manager:
```
cd dotnet/samples/Concepts
dotnet user-secrets init
dotnet user-secrets set "OpenAI:ModelId" "..."
dotnet user-secrets set "OpenAI:ChatModelId" "..."
dotnet user-secrets set "OpenAI:EmbeddingModelId" "..."
dotnet user-secrets set "OpenAI:ApiKey" "..."
```
To set your secrets with environment variables, use these names:
```
# OpenAI
OpenAI__ModelId
OpenAI__ChatModelId
OpenAI__EmbeddingModelId
OpenAI__ApiKey
```
@@ -0,0 +1,17 @@
name: GenerateStory
template: |
Tell a story about {{$topic}} that is {{$length}} sentences long.
template_format: semantic-kernel
description: A function that generates a story about a topic.
input_variables:
- name: topic
description: The topic of the story.
is_required: true
- name: length
description: The number of sentences in the story.
is_required: true
output_variable:
description: The generated story.
execution_settings:
default:
temperature: 0.6
@@ -0,0 +1,23 @@
name: GenerateStory
template: |
Tell a story about {{topic}} that is {{length}} sentences long.
template_format: handlebars
description: A function that generates a story about a topic.
input_variables:
- name: topic
description: The topic of the story.
is_required: true
- name: length
description: The number of sentences in the story.
is_required: true
output_variable:
description: The generated story.
execution_settings:
service1:
model_id: gpt-4
temperature: 0.6
service2:
model_id: gpt-3
temperature: 0.4
default:
temperature: 0.5
@@ -0,0 +1,210 @@
{
"openapi": "3.0.0",
"info": {
"title": "Repair Service",
"description": "A simple service to manage repairs for various items",
"version": "1.0.0"
},
"servers": [
{
"url": "https://piercerepairsapi.azurewebsites.net"
}
],
"paths": {
"/repairs": {
"get": {
"operationId": "listRepairs",
"summary": "List all repairs",
"description": "Returns a list of repairs with their details and images",
"parameters": [
{
"name": "assignedTo",
"in": "query",
"description": "Filter repairs by who they're assigned to",
"schema": {
"type": "string"
},
"required": false
}
],
"responses": {
"200": {
"description": "A successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "The unique identifier of the repair"
},
"title": {
"type": "string",
"description": "The short summary of the repair"
},
"description": {
"type": "string",
"description": "The detailed description of the repair"
},
"assignedTo": {
"type": "string",
"description": "The user who is responsible for the repair"
},
"date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the repair is scheduled or completed"
},
"image": {
"type": "string",
"format": "uri",
"description": "The URL of the image of the item to be repaired or the repair process"
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "createRepair",
"summary": "Create a new repair",
"description": "Adds a new repair to the list with the given details and image URL",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The short summary of the repair"
},
"description": {
"type": "string",
"description": "The detailed description of the repair"
},
"assignedTo": {
"type": "string",
"description": "The user who is responsible for the repair"
},
"date": {
"type": "string",
"format": "date-time",
"description": "The optional date and time when the repair is scheduled or completed"
},
"image": {
"type": "string",
"format": "uri",
"description": "The URL of the image of the item to be repaired or the repair process"
}
},
"required": [
"title",
"description"
]
}
}
}
},
"responses": {
"201": {
"description": "A successful response indicating that the repair was created"
}
}
},
"patch": {
"operationId": "updateRepair",
"summary": "Update an existing repair",
"description": "Update an existing repair to the list with the new updated details and image URL",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer",
"description": "The unique identifier of the repair to update"
},
"title": {
"type": "string",
"description": "The short summary of the repair"
},
"description": {
"type": "string",
"description": "The detailed description of the repair"
},
"assignedTo": {
"type": "string",
"description": "The user who is responsible for the repair"
},
"date": {
"type": "string",
"format": "date-time",
"description": "The date and time when the repair is scheduled or completed"
},
"image": {
"type": "string",
"format": "uri",
"description": "The URL of the image of the item to be repaired or the repair process"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Repair updated"
},
"404": {
"description": "Repair not found"
}
}
},
"delete": {
"operationId": "deleteRepair",
"summary": "Delete an existing repair",
"description": "Delete an existing repair from the list using its ID",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer",
"description": "The unique identifier of the repair to delete"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Repair deleted"
},
"404": {
"description": "Repair not found"
}
}
}
}
}
}
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted;
/// <summary>
/// This example shows how to create and use a <see cref="Kernel"/> with ChatClient.
/// </summary>
public sealed class Step1_Create_Kernel(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a <see cref="Kernel"/> using ChatClient and use it to execute prompts.
/// </summary>
[Fact]
public async Task CreateKernel()
{
// Create a kernel with OpenAI chat completion using ChatClient
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.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();
// 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();
// 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);
}
Console.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));
// Example 5. Invoke the kernel with a templated prompt and execution settings configured to return JSON
#pragma warning disable SKEXP0010
arguments = new(new OpenAIPromptExecutionSettings { ResponseFormat = "json_object" }) { { "topic", "chocolate" } };
Console.WriteLine(await kernel.InvokePromptAsync("Create a recipe for a {{$topic}} cake in JSON format", arguments));
}
}
@@ -0,0 +1,115 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Extensions;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted;
/// <summary>
/// This example shows how to load a <see cref="KernelPlugin"/> instances with ChatClient.
/// </summary>
public sealed class Step2_Add_Plugins(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Shows different ways to load a <see cref="KernelPlugin"/> instances with ChatClient.
/// </summary>
[Fact]
public async Task AddPlugins()
{
// Create a kernel with ChatClient and plugins
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
kernelBuilder.Plugins.AddFromType<TimeInformation>();
kernelBuilder.Plugins.AddFromType<WidgetFactory>();
Kernel kernel = kernelBuilder.Build();
// Example 1. Invoke the kernel with a prompt that asks the AI for information it cannot provide and may hallucinate
Console.WriteLine("Example 1: Asking the AI for information it cannot provide:");
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas?"));
// Example 2. Use kernel for templated prompts that invoke plugins directly
Console.WriteLine("\nExample 2: Using templated prompts that invoke plugins directly:");
Console.WriteLine(await kernel.InvokePromptAsync("The current time is {{TimeInformation.GetCurrentUtcTime}}. How many days until Christmas?"));
// Example 3. Use kernel with function calling for automatic plugin invocation
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine("\nExample 3: Using function calling for automatic plugin invocation:");
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
// Example 4. Use kernel with function calling for complex scenarios with enumerations
Console.WriteLine("\nExample 4: Using function calling for complex scenarios with enumerations:");
Console.WriteLine(await kernel.InvokePromptAsync("Create a handy lime colored widget for me.", new(settings)));
Console.WriteLine(await kernel.InvokePromptAsync("Create a beautiful scarlet colored widget for me.", new(settings)));
Console.WriteLine(await kernel.InvokePromptAsync("Create an attractive maroon and navy colored widget for me.", new(settings)));
}
/// <summary>
/// A plugin that returns the current time.
/// </summary>
public class TimeInformation
{
[KernelFunction]
[Description("Retrieves the current time in UTC.")]
public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
}
/// <summary>
/// A plugin that creates widgets.
/// </summary>
public class WidgetFactory
{
[KernelFunction]
[Description("Creates a new widget of the specified type and colors")]
public WidgetDetails CreateWidget([Description("The type of widget to be created")] WidgetType widgetType, [Description("The colors of the widget to be created")] WidgetColor[] widgetColors)
{
var colors = string.Join('-', widgetColors.Select(c => c.GetDisplayName()).ToArray());
return new()
{
SerialNumber = $"{widgetType}-{colors}-{Guid.NewGuid()}",
Type = widgetType,
Colors = widgetColors
};
}
}
/// <summary>
/// A <see cref="JsonConverter"/> is required to correctly convert enum values.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WidgetType
{
[Description("A widget that is useful.")]
Useful,
[Description("A widget that is decorative.")]
Decorative
}
/// <summary>
/// A <see cref="JsonConverter"/> is required to correctly convert enum values.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WidgetColor
{
[Description("Use when creating a red item.")]
Red,
[Description("Use when creating a green item.")]
Green,
[Description("Use when creating a blue item.")]
Blue
}
public class WidgetDetails
{
public string SerialNumber { get; init; }
public WidgetType Type { get; init; }
public WidgetColor[] Colors { get; init; }
}
}
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Resources;
namespace GettingStarted;
/// <summary>
/// This example shows how to create a prompt <see cref="KernelFunction"/> from a YAML resource.
/// </summary>
public sealed class Step3_Yaml_Prompt(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a prompt <see cref="KernelFunction"/> from a YAML resource.
/// </summary>
[Fact]
public async Task CreatePromptFromYaml()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
// Load prompt from resource
var generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml");
var function = kernel.CreateFunctionFromPromptYaml(generateStoryYaml);
// Invoke the prompt function and display the result
Console.WriteLine(await kernel.InvokeAsync(function, arguments: new()
{
{ "topic", "Dog" },
{ "length", "3" },
}));
// Load prompt from resource
var generateStoryHandlebarsYaml = EmbeddedResource.Read("GenerateStoryHandlebars.yaml");
function = kernel.CreateFunctionFromPromptYaml(generateStoryHandlebarsYaml, new HandlebarsPromptTemplateFactory());
// Invoke the prompt function and display the result
Console.WriteLine(await kernel.InvokeAsync(function, arguments: new()
{
{ "topic", "Cat" },
{ "length", "3" },
}));
}
}
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
namespace GettingStarted;
/// <summary>
/// This example shows how to using Dependency Injection with the Semantic Kernel
/// </summary>
public sealed class Step4_Dependency_Injection(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to create a <see cref="Kernel"/> that participates in Dependency Injection.
/// </summary>
[Fact]
public async Task GetKernelUsingDependencyInjection()
{
// 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
var serviceProvider = BuildServiceProvider();
var kernel = serviceProvider.GetRequiredService<Kernel>();
// Invoke the kernel with a templated prompt and stream the results to the display
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);
}
}
/// <summary>
/// Show how to use a plugin that participates in Dependency Injection.
/// </summary>
[Fact]
public async Task PluginUsingDependencyInjection()
{
// If an application follows DI guidelines, the following line is unnecessary because DI will inject an instance of the Kernel class to a class that references it.
// DI container guidelines - https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#recommendations
var serviceProvider = BuildServiceProvider();
var kernel = serviceProvider.GetRequiredService<Kernel>();
// Invoke the prompt which relies on invoking a plugin that depends on a service made available using Dependency Injection.
PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("Greet the current user by name.", new(settings)));
}
/// <summary>
/// Build a ServiceProvider that can be used to resolve services.
/// </summary>
private ServiceProvider BuildServiceProvider()
{
var collection = new ServiceCollection();
collection.AddSingleton<ILoggerFactory>(new XunitLogger(this.Output));
collection.AddSingleton<IUserService>(new FakeUserService());
// Add ChatClient using OpenAI
collection.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
var kernelBuilder = collection.AddKernel();
kernelBuilder.Plugins.AddFromType<TimeInformation>();
kernelBuilder.Plugins.AddFromType<UserInformation>();
return collection.BuildServiceProvider();
}
/// <summary>
/// A plugin that returns the current time.
/// </summary>
public class TimeInformation(ILoggerFactory loggerFactory)
{
private readonly ILogger _logger = loggerFactory.CreateLogger<TimeInformation>();
[KernelFunction]
[Description("Retrieves the current time in UTC.")]
public string GetCurrentUtcTime()
{
var utcNow = DateTime.UtcNow.ToString("R");
this._logger.LogInformation("Returning current time {0}", utcNow);
return utcNow;
}
}
/// <summary>
/// A plugin that returns the current time.
/// </summary>
public class UserInformation(IUserService userService)
{
[KernelFunction]
[Description("Retrieves the current users name.")]
public string GetUsername()
{
return userService.GetCurrentUsername();
}
}
/// <summary>
/// Interface for a service to get the current user id.
/// </summary>
public interface IUserService
{
/// <summary>
/// Return the user id for the current user.
/// </summary>
string GetCurrentUsername();
}
/// <summary>
/// Fake implementation of <see cref="IUserService"/>
/// </summary>
public class FakeUserService : IUserService
{
/// <inheritdoc/>
public string GetCurrentUsername() => "Bob";
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
namespace GettingStarted;
public sealed class Step5_Chat_Prompt(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to construct a chat prompt and invoke it.
/// </summary>
[Fact]
public async Task InvokeChatPrompt()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
// Invoke the kernel with a chat prompt and display the result
string chatPrompt = """
<message role="user">What is Seattle?</message>
<message role="system">Respond with JSON.</message>
""";
Console.WriteLine(await kernel.InvokePromptAsync(chatPrompt));
}
}
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
namespace GettingStarted;
public sealed class Step6_Responsible_AI(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Show how to use prompt filters to ensure that prompts are rendered in a responsible manner.
/// </summary>
[Fact]
public async Task AddPromptFilter()
{
// Create a kernel with OpenAI chat completion
var builder = Kernel.CreateBuilder()
.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
builder.Services.AddSingleton<ITestOutputHelper>(this.Output);
// Add prompt filter to the kernel
builder.Services.AddSingleton<IPromptRenderFilter, PromptFilter>();
var kernel = builder.Build();
KernelArguments arguments = new() { { "card_number", "4444 3333 2222 1111" } };
var result = await kernel.InvokePromptAsync("Tell me some useful information about this credit card number {{$card_number}}?", arguments);
Console.WriteLine(result);
// Output: Sorry, but I can't assist with that.
}
private sealed class PromptFilter(ITestOutputHelper output) : IPromptRenderFilter
{
private readonly ITestOutputHelper _output = output;
/// <summary>
/// Method which is called asynchronously before prompt rendering.
/// </summary>
/// <param name="context">Instance of <see cref="PromptRenderContext"/> with prompt rendering details.</param>
/// <param name="next">Delegate to the next filter in pipeline or prompt rendering operation itself. If it's not invoked, next filter or prompt rendering won't be invoked.</param>
public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
{
if (context.Arguments.ContainsName("card_number"))
{
context.Arguments["card_number"] = "**** **** **** ****";
}
await next(context);
context.RenderedPrompt += " NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY";
this._output.WriteLine(context.RenderedPrompt);
}
}
}
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace GettingStarted;
public sealed class Step7_Observability(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Shows how to observe the execution of a <see cref="KernelPlugin"/> instance with filters.
/// </summary>
[Fact]
public async Task ObservabilityWithFilters()
{
// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
kernelBuilder.Plugins.AddFromType<TimeInformation>();
// Add filter using DI
kernelBuilder.Services.AddSingleton<ITestOutputHelper>(this.Output);
kernelBuilder.Services.AddSingleton<IFunctionInvocationFilter, MyFunctionFilter>();
Kernel kernel = kernelBuilder.Build();
// Add filter without DI
kernel.PromptRenderFilters.Add(new MyPromptFilter(this.Output));
// Invoke the kernel with a prompt and allow the AI to automatically invoke functions
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings)));
}
/// <summary>
/// A plugin that returns the current time.
/// </summary>
private sealed class TimeInformation
{
[KernelFunction]
[Description("Retrieves the current time in UTC.")]
public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
}
/// <summary>
/// Function filter for observability.
/// </summary>
private sealed class MyFunctionFilter(ITestOutputHelper output) : IFunctionInvocationFilter
{
private readonly ITestOutputHelper _output = output;
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
this._output.WriteLine($"Invoking {context.Function.Name}");
await next(context);
var metadata = context.Result?.Metadata;
if (metadata is not null && metadata.ContainsKey("Usage"))
{
this._output.WriteLine($"Token usage: {metadata["Usage"]?.AsJson()}");
}
}
}
/// <summary>
/// Prompt filter for observability.
/// </summary>
private sealed class MyPromptFilter(ITestOutputHelper output) : IPromptRenderFilter
{
private readonly ITestOutputHelper _output = output;
public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
{
this._output.WriteLine($"Rendering prompt for {context.Function.Name}");
await next(context);
this._output.WriteLine($"Rendered prompt: {context.RenderedPrompt}");
}
}
}
@@ -0,0 +1,173 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
namespace GettingStarted;
public sealed class Step8_Pipelining(ITestOutputHelper output) : BaseTest(output)
{
/// <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>
[Fact]
public async Task CreateFunctionPipeline()
{
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatClient(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey);
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Trace));
Kernel kernel = builder.Build();
Console.WriteLine("================ PIPELINE ================");
{
// Create a pipeline of functions that will parse a string into a double, multiply it by a double, truncate it to an int, and then humanize it.
KernelFunction parseDouble = KernelFunctionFactory.CreateFromMethod((string s) => double.Parse(s, CultureInfo.InvariantCulture), "parseDouble");
KernelFunction multiplyByN = KernelFunctionFactory.CreateFromMethod((double i, double n) => i * n, "multiplyByN");
KernelFunction truncate = KernelFunctionFactory.CreateFromMethod((double d) => (int)d, "truncate");
KernelFunction humanize = KernelFunctionFactory.CreateFromPrompt(new PromptTemplateConfig()
{
Template = "Spell out this number in English: {{$number}}",
InputVariables = [new() { Name = "number" }],
});
KernelFunction pipeline = KernelFunctionCombinators.Pipe([parseDouble, multiplyByN, truncate, humanize], "pipeline");
KernelArguments args = new()
{
["s"] = "123.456",
["n"] = (double)78.90,
};
// - 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));
}
Console.WriteLine("================ GRAPH ================");
{
KernelFunction rand = KernelFunctionFactory.CreateFromMethod(() => Random.Shared.Next(), "GetRandomInt32");
KernelFunction mult = KernelFunctionFactory.CreateFromMethod((int i, int j) => i * j, "Multiply");
// - Invokes rand and stores the random number into args["i"]
// - Invokes rand and stores the random number into args["j"]
// - Multiplies arg["i"] and args["j"] to produce the final result
KernelFunction graph = KernelFunctionCombinators.Pipe(new[]
{
(rand, "i"),
(rand, "j"),
(mult, "")
}, "graph");
Console.WriteLine(await graph.InvokeAsync(kernel));
}
}
}
public static class KernelFunctionCombinators
{
/// <summary>
/// Invokes a pipeline of functions, running each in order and passing the output from one as the first argument to the next.
/// </summary>
/// <param name="functions">The pipeline of functions to invoke.</param>
/// <param name="kernel">The kernel to use for the operations.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="cancellationToken">The cancellation token to monitor for a cancellation request.</param>
public static Task<FunctionResult> InvokePipelineAsync(
IEnumerable<KernelFunction> functions, Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) =>
Pipe(functions).InvokeAsync(kernel, arguments, cancellationToken);
/// <summary>
/// Invokes a pipeline of functions, running each in order and passing the output from one as the named argument to the next.
/// </summary>
/// <param name="functions">The sequence of functions to invoke, along with the name of the argument to assign to the result of the function's invocation.</param>
/// <param name="kernel">The kernel to use for the operations.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="cancellationToken">The cancellation token to monitor for a cancellation request.</param>
public static Task<FunctionResult> InvokePipelineAsync(
IEnumerable<(KernelFunction Function, string OutputVariable)> functions, Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) =>
Pipe(functions).InvokeAsync(kernel, arguments, cancellationToken);
/// <summary>
/// Creates a function whose invocation will invoke each of the supplied functions in sequence.
/// </summary>
/// <param name="functions">The pipeline of functions to invoke.</param>
/// <param name="functionName">The name of the combined operation.</param>
/// <param name="description">The description of the combined operation.</param>
/// <returns>The result of the final function.</returns>
/// <remarks>
/// The result from one function will be fed into the first argument of the next function.
/// </remarks>
public static KernelFunction Pipe(
IEnumerable<KernelFunction> functions,
string? functionName = null,
string? description = null)
{
ArgumentNullException.ThrowIfNull(functions);
KernelFunction[] funcs = functions.ToArray();
Array.ForEach(funcs, f => ArgumentNullException.ThrowIfNull(f));
var funcsAndVars = new (KernelFunction Function, string OutputVariable)[funcs.Length];
for (int i = 0; i < funcs.Length; i++)
{
string p = "";
if (i < funcs.Length - 1)
{
var parameters = funcs[i + 1].Metadata.Parameters;
if (parameters.Count > 0)
{
p = parameters[0].Name;
}
}
funcsAndVars[i] = (funcs[i], p);
}
return Pipe(funcsAndVars, functionName, description);
}
/// <summary>
/// Creates a function whose invocation will invoke each of the supplied functions in sequence.
/// </summary>
/// <param name="functions">The pipeline of functions to invoke, along with the name of the argument to assign to the result of the function's invocation.</param>
/// <param name="functionName">The name of the combined operation.</param>
/// <param name="description">The description of the combined operation.</param>
/// <returns>The result of the final function.</returns>
/// <remarks>
/// The result from one function will be fed into the first argument of the next function.
/// </remarks>
public static KernelFunction Pipe(
IEnumerable<(KernelFunction Function, string OutputVariable)> functions,
string? functionName = null,
string? description = null)
{
ArgumentNullException.ThrowIfNull(functions);
(KernelFunction Function, string OutputVariable)[] arr = functions.ToArray();
Array.ForEach(arr, f =>
{
ArgumentNullException.ThrowIfNull(f.Function);
ArgumentNullException.ThrowIfNull(f.OutputVariable);
});
return KernelFunctionFactory.CreateFromMethod(async (Kernel kernel, KernelArguments arguments) =>
{
FunctionResult? result = null;
for (int i = 0; i < arr.Length; i++)
{
result = await arr[i].Function.InvokeAsync(kernel, arguments).ConfigureAwait(false);
if (i < arr.Length - 1)
{
arguments[arr[i].OutputVariable] = result.GetValue<object>();
}
}
return result;
}, functionName, description);
}
}
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Resources;
namespace GettingStarted;
/// <summary>
/// This example shows how to load an Open API <see cref="KernelPlugin"/> instance.
/// </summary>
public sealed class Step9_OpenAPI_Plugins(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Shows how to load an Open API <see cref="KernelPlugin"/> instance.
/// </summary>
[Fact]
public async Task AddOpenAPIPlugins()
{
// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
Kernel kernel = kernelBuilder.Build();
// Load OpenAPI plugin
var stream = EmbeddedResource.ReadStream("repair-service.json");
var plugin = await kernel.ImportPluginFromOpenApiAsync("RepairService", stream!);
PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("List all of the repairs .", new(settings)));
}
/// <summary>
/// Shows how to transform an Open API <see cref="KernelPlugin"/> instance to support dependency injection with ChatClient.
/// </summary>
[Fact]
public async Task TransformOpenAPIPlugins()
{
// Create a kernel with ChatClient and dependency injection
var serviceProvider = BuildServiceProvider();
var kernel = serviceProvider.GetRequiredService<Kernel>();
// Load OpenAPI plugin
var stream = EmbeddedResource.ReadStream("repair-service.json");
var plugin = await kernel.CreatePluginFromOpenApiAsync("RepairService", stream!);
// Transform the plugin to use IMechanicService via dependency injection
kernel.Plugins.Add(TransformPlugin(plugin));
PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("Book an appointment to drain the old engine oil and replace it with fresh oil.", new(settings)));
}
/// <summary>
/// Build a ServiceProvider that can be used to resolve services.
/// </summary>
private ServiceProvider BuildServiceProvider()
{
var collection = new ServiceCollection();
collection.AddSingleton<IMechanicService>(new FakeMechanicService());
// Add ChatClient using OpenAI
collection.AddOpenAIChatClient(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);
var kernelBuilder = collection.AddKernel();
return collection.BuildServiceProvider();
}
/// <summary>
/// Transform the plugin to change the behavior of the createRepair function.
/// </summary>
public static KernelPlugin TransformPlugin(KernelPlugin plugin)
{
List<KernelFunction>? functions = [];
foreach (KernelFunction function in plugin)
{
if (function.Name == "createRepair")
{
functions.Add(CreateRepairFunction(function));
}
else
{
functions.Add(function);
}
}
return KernelPluginFactory.CreateFromFunctions(plugin.Name, plugin.Description, functions);
}
/// <summary>
/// Create a <see cref="KernelFunction"/> instance for the createRepair operation which only takes
/// the title, description parameters and has a delegate which uses the IMechanicService to get the
/// assignedTo.
/// </summary>
private static KernelFunction CreateRepairFunction(KernelFunction function)
{
var method = (
Kernel kernel,
KernelFunction currentFunction,
KernelArguments arguments,
[FromKernelServices] IMechanicService mechanicService,
CancellationToken cancellationToken) =>
{
arguments.Add("assignedTo", mechanicService.GetMechanic());
arguments.Add("date", DateTime.UtcNow.ToString("R"));
return function.InvokeAsync(kernel, arguments, cancellationToken);
};
var options = new KernelFunctionFromMethodOptions()
{
FunctionName = function.Name,
Description = function.Description,
Parameters = function.Metadata.Parameters.Where(p => p.Name is "title" or "description").ToList(),
ReturnParameter = function.Metadata.ReturnParameter,
};
return KernelFunctionFactory.CreateFromMethod(method, options);
}
/// <summary>
/// Interface for a service to get the mechanic to assign to the next job.
/// </summary>
public interface IMechanicService
{
/// <summary>
/// Return the name of the mechanic to assign the next job to.
/// </summary>
string GetMechanic();
}
/// <summary>
/// Fake implementation of <see cref="IMechanicService"/>
/// </summary>
public class FakeMechanicService : IMechanicService
{
/// <inheritdoc/>
public string GetMechanic() => "Bob";
}
}