Skip to content

Commit

Permalink
.Net: Samples showing use of allOf, anyof and oneOf (#9697)
Browse files Browse the repository at this point in the history
### Motivation and Context

Related to #8997 

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### 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 19, 2024
1 parent f4b6ac1 commit 5197f0b
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 6 deletions.
18 changes: 18 additions & 0 deletions dotnet/samples/Concepts/Concepts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Resources\Plugins\EventPlugin\openapiV1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Plugins\EventPlugin\openapiV2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Plugins\PetsPlugin\allOfV3.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Plugins\PetsPlugin\anyOfV3.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Resources\Plugins\PetsPlugin\oneOfV3.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Resources\Plugins\CopilotAgentPlugins\**\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand All @@ -128,6 +143,9 @@
<ItemGroup>
<None Remove="Resources\Plugins\EventPlugin\openapiV1.json" />
<None Remove="Resources\Plugins\EventPlugin\openapiV2.json" />
<None Remove="Resources\Plugins\PetsPlugin\allOfV3.json" />
<None Remove="Resources\Plugins\PetsPlugin\anyOfV3.json" />
<None Remove="Resources\Plugins\PetsPlugin\oneOfV3.json" />
</ItemGroup>
<ItemGroup>
<None Update="Resources\Plugins\ProductsPlugin\openapi.json">
Expand Down
96 changes: 90 additions & 6 deletions dotnet/samples/Concepts/Plugins/OpenApiPlugin_PayloadHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ public sealed class OpenApiPlugin_PayloadHandling : BaseTest
public OpenApiPlugin_PayloadHandling(ITestOutputHelper output) : base(output)
{
IKernelBuilder builder = Kernel.CreateBuilder();

builder.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
builder.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey);

this._kernel = builder.Build();

this._output = output;

void RequestPayloadHandler(string requestPayload)
{
this._output.WriteLine("Request payload");
this._output.WriteLine("Actual request payload");
this._output.WriteLine(requestPayload);
}

Expand Down Expand Up @@ -288,6 +286,92 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadLeafPropertiesWith
await this._kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
}

/// <summary>
/// This sample demonstrates how to invoke an OpenAPI function with arguments for payload using oneOf.
/// </summary>
[Fact]
public async Task InvokeOpenApiFunctionWithArgumentsForPayloadOneOfAsync()
{
// Load an Open API document for the Event Utils service
using Stream stream = File.OpenRead("Resources/Plugins/PetsPlugin/oneOfV3.json");

// Import an OpenAPI document as SK plugin
KernelPlugin plugin = await this._kernel.ImportPluginFromOpenApiAsync("Pets", stream, new OpenApiFunctionExecutionParameters(this._httpClient)
{
EnableDynamicPayload = false // Disable dynamic payload construction. It is enabled by default.
});

// Example of how to have the updatePater function invoked by the AI
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine("\nExpected payload: Dog { breed=Husky, bark=false }");
await this._kernel.InvokePromptAsync("My new dog is a Husky, he is very quiet, please create my pet information.", new KernelArguments(settings));
Console.WriteLine("\nExpected payload: Dog { breed=Dingo, bark=true }");
await this._kernel.InvokePromptAsync("My dog is a Dingo, he is very noisy, he likes to hunt for rabbits, please update my pet information.", new KernelArguments(settings));
Console.WriteLine("\nExpected payload: Cat { age=15 }");
await this._kernel.InvokePromptAsync("My cat is 15 years old now, please update my pet information.", new KernelArguments(settings));
Console.WriteLine("\nExpected payload: Cat { hunts=true }");
await this._kernel.InvokePromptAsync("I have a feline pet, she goes out every night hunting mice, please update my pet information.", new KernelArguments(settings));
Console.WriteLine("\nExpected payload: Cat { age=3, hunts=true }");
Console.WriteLine(await this._kernel.InvokePromptAsync("I have a new 3 year old cat who chases birds and barks, please create my pet information.", new KernelArguments(settings)));
}

/// <summary>
/// This sample demonstrates how to invoke an OpenAPI function with arguments for payload using allOf.
/// </summary>
[Fact]
public async Task InvokeOpenApiFunctionWithArgumentsForPayloadAllOfAsync()
{
// Load an Open API document for the Event Utils service
using Stream stream = File.OpenRead("Resources/Plugins/PetsPlugin/allOfV3.json");

// Import an OpenAPI document as SK plugin
KernelPlugin plugin = await this._kernel.ImportPluginFromOpenApiAsync("Pets", stream, new OpenApiFunctionExecutionParameters(this._httpClient)
{
EnableDynamicPayload = false // Disable dynamic payload construction. It is enabled by default.
});

// Example of how to have the updatePater function invoked by the AI
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine("\nExpected payload: { pet_type=dog, breed=Husky, bark=false }");
Console.WriteLine(await this._kernel.InvokePromptAsync("My new dog is a Husky, he is very quiet, please update my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=dog, breed=Dingo, bark=true }");
// This prompt deliberately tries to confuse the LLM and it succeed, in this scenario the API must provide an error message so the LLM can correct the playload
Console.WriteLine(await this._kernel.InvokePromptAsync("My new dog is a Dingo, he is very noisy, he likes to hunt for rabbits, please create my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=cat, age=15 }");
Console.WriteLine(await this._kernel.InvokePromptAsync("My cat is 15 years old now, please update my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=cat, hunts=true }");
Console.WriteLine(await this._kernel.InvokePromptAsync("I have a feline pet, she goes out every night hunting mice, please update my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=cat, age=3, hunts=true }");
Console.WriteLine(await this._kernel.InvokePromptAsync("I have a new 3 year old cat who chases birds and barks, please create my pet information.", new KernelArguments(settings)));
}

/// <summary>
/// This sample demonstrates how to invoke an OpenAPI function with arguments for payload using anyOf.
/// </summary>
[Fact]
public async Task InvokeOpenApiFunctionWithArgumentsForPayloadAnyOfAsync()
{
// Load an Open API document for the Event Utils service
using Stream stream = File.OpenRead("Resources/Plugins/PetsPlugin/anyOfV3.json");

// Import an OpenAPI document as SK plugin
KernelPlugin plugin = await this._kernel.ImportPluginFromOpenApiAsync("Pets", stream, new OpenApiFunctionExecutionParameters(this._httpClient)
{
EnableDynamicPayload = false // Disable dynamic payload construction. It is enabled by default.
});

// Example of how to have the updatePater function invoked by the AI
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine("\nExpected payload: { pet_type=Dog, nickname=Fido }");
Console.WriteLine(await this._kernel.InvokePromptAsync("My new dog is named Fido he is 2 years old, please create my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=Dog, nickname=Spot age=1 hunts=true }");
Console.WriteLine(await this._kernel.InvokePromptAsync("My 1 year old dog is called Spot, he likes to hunt for rabbits, please update my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=Cat, age=15 }");
Console.WriteLine(await this._kernel.InvokePromptAsync("My cat is 15 years old now, please update my pet information.", new KernelArguments(settings)));
Console.WriteLine("\nExpected payload: { pet_type=Cat, nick_name=Fluffy }");
Console.WriteLine(await this._kernel.InvokePromptAsync("I have a new feline pet called Fluffy, please create my pet information.", new KernelArguments(settings)));
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Expand Down
116 changes: 116 additions & 0 deletions dotnet/samples/Concepts/Resources/Plugins/PetsPlugin/allOfV3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"openapi": "3.0.1",
"info": {
"title": "Pets API",
"version": "1.0.0",
"description": "API for managing pets."
},
"servers": [
{
"url": "https://api.yourdomain.com"
}
],
"paths": {
"/pets": {
"patch": {
"summary": "Update a pet. Call this with either details for a dog or a cat but not both.",
"description": "Update a pet. Call this with either details for a dog or a cat but not both.",
"operationId": "updatePet",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{ "$ref": "#/components/schemas/Cat" },
{ "$ref": "#/components/schemas/Dog" }
],
"discriminator": {
"propertyName": "pet_type"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated"
}
}
},
"post": {
"summary": "Create a pet. Call this with either details for a dog or a cat but not both.",
"description": "Create a pet. Call this with either details for a dog or a cat but not both.",
"operationId": "createPet",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{ "$ref": "#/components/schemas/Cat" },
{ "$ref": "#/components/schemas/Dog" }
],
"discriminator": {
"propertyName": "pet_type"
}
}
}
}
},
"responses": {
"201": {
"description": "Created"
}
}
}
}
},
"components": {
"schemas": {
"Pet": {
"type": "object",
"required": [ "pet_type" ],
"properties": {
"pet_type": {
"type": "string"
}
},
"discriminator": {
"propertyName": "pet_type"
}
},
"Dog": {
"allOf": [
{ "$ref": "#/components/schemas/Pet" },
{
"type": "object",
"properties": {
"bark": {
"type": "boolean"
},
"breed": {
"type": "string",
"enum": [ "Dingo", "Husky", "Retriever", "Shepherd" ]
}
}
}
]
},
"Cat": {
"allOf": [
{ "$ref": "#/components/schemas/Pet" },
{
"type": "object",
"properties": {
"hunts": {
"type": "boolean"
},
"age": {
"type": "integer"
}
}
}
]
}
}
}
}
90 changes: 90 additions & 0 deletions dotnet/samples/Concepts/Resources/Plugins/PetsPlugin/anyOfV3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"openapi": "3.0.1",
"info": {
"title": "Pets API",
"version": "1.0.0",
"description": "API for managing pets."
},
"servers": [
{
"url": "https://api.yourdomain.com"
}
],
"paths": {
"/pets": {
"patch": {
"summary": "Update a pet. Call this with either details for a dog or a cat but not both.",
"description": "Update a pet. Call this with either details for a dog or a cat but not both.",
"operationId": "updatePet",
"requestBody": {
"content": {
"application/json": {
"schema": {
"anyOf": [
{ "$ref": "#/components/schemas/PetByAge" },
{ "$ref": "#/components/schemas/PetByType" }
]
}
}
}
},
"responses": {
"200": {
"description": "Updated"
}
}
},
"put": {
"summary": "Create a pet. Call this with either details for a dog or a cat but not both.",
"description": "Create a pet. Call this with either details for a dog or a cat but not both.",
"operationId": "createPet",
"requestBody": {
"content": {
"application/json": {
"schema": {
"anyOf": [
{ "$ref": "#/components/schemas/PetByAge" },
{ "$ref": "#/components/schemas/PetByType" }
]
}
}
}
},
"responses": {
"201": {
"description": "Create"
}
}
}
}
},
"components": {
"schemas": {
"PetByAge": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"nickname": {
"type": "string"
}
},
"required": [ "age" ]
},
"PetByType": {
"type": "object",
"properties": {
"pet_type": {
"type": "string",
"enum": [ "Cat", "Dog" ]
},
"hunts": {
"type": "boolean"
}
},
"required": [ "pet_type" ]
}
}
}
}
Loading

0 comments on commit 5197f0b

Please sign in to comment.