Skip to content

Commit

Permalink
.Net: Improve the DI sample and function naming (#9794)
Browse files Browse the repository at this point in the history
### Motivation and Context

Create samples that uses a plugin which depends on a service made
available using dependency injection
- Native function
- Open API function

Closes #9769 

### Description

A frequent ask is how to inject a service using DI which will be used
during function execution.

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [ ] The code builds clean without any errors or warnings
- [ ] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
  • Loading branch information
markwallace-microsoft authored Nov 25, 2024
1 parent 5ae74d7 commit 33c1de6
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 8 deletions.
6 changes: 6 additions & 0 deletions dotnet/samples/GettingStarted/GettingStarted.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
<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" />
Expand Down
210 changes: 210 additions & 0 deletions dotnet/samples/GettingStarted/Resources/repair-service.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step1_Create_Kernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class Step1_Create_Kernel(ITestOutputHelper output) : BaseTest(out
/// Show how to create a <see cref="Kernel"/> and use it to execute prompts.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task CreateKernelAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
Expand Down
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step2_Add_Plugins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class Step2_Add_Plugins(ITestOutputHelper output) : BaseTest(outpu
/// Shows different ways to load a <see cref="KernelPlugin"/> instances.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task AddPluginsAsync()
{
// Create a kernel with OpenAI chat completion
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
Expand Down
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step3_Yaml_Prompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class Step3_Yaml_Prompt(ITestOutputHelper output) : BaseTest(outpu
/// Show how to create a prompt <see cref="KernelFunction"/> from a YAML resource.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task CreatPromptFromYamlAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
Expand Down
55 changes: 53 additions & 2 deletions dotnet/samples/GettingStarted/Step4_Dependency_Injection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class Step4_Dependency_Injection(ITestOutputHelper output) : BaseT
/// Show how to create a <see cref="Kernel"/> that participates in Dependency Injection.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task GetKernelUsingDependencyInjectionAsync()
{
// 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
Expand All @@ -32,17 +32,35 @@ public async Task RunAsync()
}
}

/// <summary>
/// Show how to use a plugin that participates in Dependency Injection.
/// </summary>
[Fact]
public async Task PluginUsingDependencyInjectionAsync()
{
// 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 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());

var kernelBuilder = collection.AddKernel();
kernelBuilder.Services.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
kernelBuilder.Plugins.AddFromType<TimeInformation>();
kernelBuilder.Plugins.AddFromType<UserInformation>();

return collection.BuildServiceProvider();
}
Expand All @@ -52,7 +70,7 @@ private ServiceProvider BuildServiceProvider()
/// </summary>
public class TimeInformation(ILoggerFactory loggerFactory)
{
private readonly ILogger _logger = loggerFactory.CreateLogger(typeof(TimeInformation));
private readonly ILogger _logger = loggerFactory.CreateLogger<TimeInformation>();

[KernelFunction]
[Description("Retrieves the current time in UTC.")]
Expand All @@ -63,4 +81,37 @@ public string GetCurrentUtcTime()
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";
}
}
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step5_Chat_Prompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class Step5_Chat_Prompt(ITestOutputHelper output) : BaseTest(outpu
/// Show how to construct a chat prompt and invoke it.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task InvokeChatPromptAsync()
{
// Create a kernel with OpenAI chat completion
Kernel kernel = Kernel.CreateBuilder()
Expand Down
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step6_Responsible_AI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public sealed class Step6_Responsible_AI(ITestOutputHelper output) : BaseTest(ou
/// Show how to use prompt filters to ensure that prompts are rendered in a responsible manner.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task AddPromptFilterAsync()
{
// Create a kernel with OpenAI chat completion
var builder = Kernel.CreateBuilder()
Expand Down
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/Step8_Pipelining.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class Step8_Pipelining(ITestOutputHelper output) : BaseTest(output
/// them in a sequence, passing the output from one as input to the next.
/// </summary>
[Fact]
public async Task RunAsync()
public async Task CreateFunctionPipelineAsync()
{
IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
Expand Down
Loading

0 comments on commit 33c1de6

Please sign in to comment.