Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for testing OpenAI plugins using URL #5

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# skUnit
[![Build and Deploy](https://github.com/mehrandvd/skUnit/actions/workflows/build.yml/badge.svg)](https://github.com/mehrandvd/skUnit/actions/workflows/build.yml)
[![NuGet version (skUnit)](https://img.shields.io/nuget/v/skUnit.svg?style=flat)](https://www.nuget.org/packages/skUnit/)
[![NuGet downloads](https://img.shields.io/nuget/dt/skUnit.svg?style=flat)](https://www.nuget.org/packages/skUnit)
[![NuGet downloads](https://img.shields.io/nuget/dt/skUnit.svg?style=flat)](https://www.nuget.org/packages/skUnit/)

**skUnit** is a testing tool for [SemanticKernel](https://github.com/microsoft/semantic-kernel) units, such as _plugin functions_ and _kernels_.

Expand Down Expand Up @@ -121,6 +121,35 @@ Here's another example of an executing The [Chatting about Eiffel height](https:

![image](https://github.com/mehrandvd/skunit/assets/5070766/56bc08fe-0955-4ed4-9b4c-5d4ff416b3d3)

## Testing OpenAI Plugins Using URL

skUnit now supports testing OpenAI plugins directly using their URL. This feature allows you to load and test plugins without needing to pre-configure them in your kernel.

### Example

Here is an example of how to test an OpenAI plugin using its URL:

```csharp
public class MyTest
{
SemanticKernelAssert SemanticKernelAssert { get; set; }
MyTest(ITestOutputHelper output)
{
var pluginUrl = "https://example.com/openai-plugin";
SemanticKernelAssert = new SemanticKernelAssert(pluginUrl, message => output.WriteLine(message));
}

[Fact]
public async Task MyPluginShouldWork()
{
var scenarios = await ChatScenario.LoadFromResourceAsync("MyPluginScenario.md", Assembly.GetExecutingAssembly());
await SemanticKernelAssert.CheckPluginByUrlAsync(scenarios);
}
}
```

In this example, the `SemanticKernelAssert` class is initialized with the URL of the OpenAI plugin. The `CheckPluginByUrlAsync` method is then used to test the plugin with the provided scenarios.

## Documents
To better understand skUnit, Check these documents:
- [Invocation Scenario Spec](https://github.com/mehrandvd/skunit/blob/main/docs/invocation-scenario-spec.md): The details of writing an InvocationScenario.
Expand Down
30 changes: 29 additions & 1 deletion src/skUnit/Asserts/SemanticKernelAssert_Chat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,32 @@
Log();
}
}
}

/// <summary>
/// Checks whether the <paramref name="scenario"/> passes on the given plugin URL
/// using its ChatCompletionService.
/// </summary>
/// <param name="pluginUrl"></param>
/// <param name="scenario"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">If the OpenAI was unable to generate a valid response.</exception>
public async Task CheckPluginByUrlAsync(string pluginUrl, ChatScenario scenario)
{
var kernel = new KernelBuilder().WithOpenAIChatCompletionService(pluginUrl).Build();

Check failure on line 204 in src/skUnit/Asserts/SemanticKernelAssert_Chat.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level

Check failure on line 204 in src/skUnit/Asserts/SemanticKernelAssert_Chat.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level
await CheckChatScenarioAsync(kernel, scenario);
}

/// <summary>
/// Checks whether all of the <paramref name="scenarios"/> passes on the given plugin URL
/// using its ChatCompletionService.
/// </summary>
/// <param name="pluginUrl"></param>
/// <param name="scenarios"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">If the OpenAI was unable to generate a valid response.</exception>
public async Task CheckPluginByUrlAsync(string pluginUrl, List<ChatScenario> scenarios)
{
var kernel = new KernelBuilder().WithOpenAIChatCompletionService(pluginUrl).Build();

Check failure on line 218 in src/skUnit/Asserts/SemanticKernelAssert_Chat.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level

Check failure on line 218 in src/skUnit/Asserts/SemanticKernelAssert_Chat.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level
await CheckChatScenarioAsync(kernel, scenarios);
}
}
38 changes: 37 additions & 1 deletion src/skUnit/Asserts/SemanticKernelAssert_Function.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@

try
{
await assertion.Assert(Semantic, result);

Check warning on line 126 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'input' in 'Task IKernelAssertion.Assert(Semantic semantic, string input)'.
Log($"✅ OK");
Log("");
}
Expand Down Expand Up @@ -178,4 +178,40 @@
Log("");
}
}
}

/// <summary>
/// Checks whether the <paramref name="pluginUrl"/> can pass the <paramref name="scenario"/>.
/// </summary>
/// <remarks>
/// It runs the scenario using:
/// <code>kernel.InvokeAsync</code>
/// and checks all the assertions specified within the scenario.
/// </remarks>
/// <param name="pluginUrl"></param>
/// <param name="scenario"></param>
/// <returns></returns>
public async Task CheckPluginByUrlAsync(string pluginUrl, InvocationScenario scenario)
{
var kernel = new KernelBuilder().WithOpenAIChatCompletionService(pluginUrl).Build();

Check failure on line 195 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level

Check failure on line 195 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level
var function = kernel.GetFunction("pluginFunctionName"); // Replace with actual function name

Check failure on line 196 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'functionName' of 'KernelPluginExtensions.GetFunction(IReadOnlyKernelPluginCollection, string?, string)'
await CheckScenarioAsync(kernel, function, scenario);
}

/// <summary>
/// Checks whether the <paramref name="pluginUrl"/> can pass all the <paramref name="scenarios"/>.
/// </summary>
/// <remarks>
/// It runs the scenario using:
/// <code>kernel.InvokeAsync</code>
/// and checks all the assertions specified within the scenario.
/// </remarks>
/// <param name="pluginUrl"></param>
/// <param name="scenarios"></param>
/// <returns></returns>
public async Task CheckPluginByUrlAsync(string pluginUrl, List<InvocationScenario> scenarios)
{
var kernel = new KernelBuilder().WithOpenAIChatCompletionService(pluginUrl).Build();

Check failure on line 213 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level
var function = kernel.GetFunction("pluginFunctionName"); // Replace with actual function name

Check failure on line 214 in src/skUnit/Asserts/SemanticKernelAssert_Function.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'functionName' of 'KernelPluginExtensions.GetFunction(IReadOnlyKernelPluginCollection, string?, string)'
await CheckScenarioAsync(kernel, function, scenarios);
}
}
20 changes: 20 additions & 0 deletions src/skUnit/Asserts/SemanticKernelAssert_Initialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,31 @@

}

/// <summary>
/// This class needs a SemanticKernel kernel to work.
/// Using this constructor you can use a plugin URL to configure it.
/// </summary>
/// <param name="pluginUrl"></param>
/// <param name="onLog">If you're using xUnit, do this in the constructor:
/// <code>
/// MyTest(ITestOutputHelper output)
/// {
/// SemanticKernelAssert = new SemanticKernelAssert(_pluginUrl, output.WriteLine);
/// }
/// </code>
/// </param>
public SemanticKernelAssert(string pluginUrl, Action<string> onLog)
{
var kernel = new KernelBuilder().WithOpenAIChatCompletionService(pluginUrl).Build();

Check failure on line 73 in src/skUnit/Asserts/SemanticKernelAssert_Initialize.cs

View workflow job for this annotation

GitHub Actions / build

'KernelBuilder' is inaccessible due to its protection level
Semantic = new Semantic(kernel);
OnLog = onLog;
}

private void Log(string? message = "")
{
if (OnLog is not null)
{
OnLog(message);

Check warning on line 82 in src/skUnit/Asserts/SemanticKernelAssert_Initialize.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'obj' in 'void Action<string>.Invoke(string obj)'.
}
}

Expand Down
Loading