chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user