Skip to content

Commit

Permalink
.Net: Add some Kernel checks to the RequiredFunction behavior (#5637)
Browse files Browse the repository at this point in the history
### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

### 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 😄

---------

Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 26, 2024
1 parent 9aed71f commit 41a85b2
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 3 deletions.
22 changes: 21 additions & 1 deletion dotnet/src/Connectors/Connectors.OpenAI/ToolCallBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private ToolCallBehavior(bool autoInvoke)

/// <summary>
/// Represents a <see cref="ToolCallBehavior"/> that will provide to the model all available functions from a
/// <see cref="Kernel"/> provided by the client.
/// <see cref="Kernel"/> provided by the client. Setting this will have no effect if no <see cref="Kernel"/> is provided.
/// </summary>
internal sealed class KernelFunctions : ToolCallBehavior
{
Expand Down Expand Up @@ -216,11 +216,13 @@ internal override void ConfigureOptions(Kernel? kernel, ChatCompletionsOptions o
/// <summary>Represents a <see cref="ToolCallBehavior"/> that requests the model use a specific function.</summary>
internal sealed class RequiredFunction : ToolCallBehavior
{
private readonly OpenAIFunction _function;
private readonly ChatCompletionsFunctionToolDefinition _tool;
private readonly ChatCompletionsToolChoice _choice;

public RequiredFunction(OpenAIFunction function, bool autoInvoke) : base(autoInvoke)
{
this._function = function;
this._tool = new ChatCompletionsFunctionToolDefinition(function.ToFunctionDefinition());
this._choice = new ChatCompletionsToolChoice(this._tool);
}
Expand All @@ -229,6 +231,24 @@ public RequiredFunction(OpenAIFunction function, bool autoInvoke) : base(autoInv

internal override void ConfigureOptions(Kernel? kernel, ChatCompletionsOptions options)
{
bool autoInvoke = base.MaximumAutoInvokeAttempts > 0;

// If auto-invocation is specified, we need a kernel to be able to invoke the functions.
// Lack of a kernel is fatal: we don't want to tell the model we can handle the functions
// and then fail to do so, so we fail before we get to that point. This is an error
// on the consumers behalf: if they specify auto-invocation with any functions, they must
// specify the kernel and the kernel must contain those functions.
if (autoInvoke && kernel is null)
{
throw new KernelException($"Auto-invocation with {nameof(RequiredFunction)} is not supported when no kernel is provided.");
}

// Make sure that if auto-invocation is specified, the required function can be found in the kernel.
if (autoInvoke && !kernel!.Plugins.TryGetFunction(this._function.PluginName, this._function.FunctionName, out _))
{
throw new KernelException($"The specified {nameof(RequiredFunction)} function {this._function.FullyQualifiedName} is not available in the kernel.");
}

options.ToolChoice = this._choice;
options.Tools.Add(this._tool);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,46 @@ public void EnabledFunctionsConfigureOptionsWithKernelAndPluginsAddsTools(bool a
this.AssertTools(chatCompletionsOptions);
}

[Fact]
public void RequiredFunctionsConfigureOptionsWithAutoInvokeAndNullKernelThrowsException()
{
// Arrange
var function = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToOpenAIFunction()).First();
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
var chatCompletionsOptions = new ChatCompletionsOptions();

// Act & Assert
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(null, chatCompletionsOptions));
Assert.Equal($"Auto-invocation with {nameof(RequiredFunction)} is not supported when no kernel is provided.", exception.Message);
}

[Fact]
public void RequiredFunctionsConfigureOptionsWithAutoInvokeAndEmptyKernelThrowsException()
{
// Arrange
var function = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToOpenAIFunction()).First();
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
var chatCompletionsOptions = new ChatCompletionsOptions();
var kernel = Kernel.CreateBuilder().Build();

// Act & Assert
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(kernel, chatCompletionsOptions));
Assert.Equal($"The specified {nameof(RequiredFunction)} function MyPlugin-MyFunction is not available in the kernel.", exception.Message);
}

[Fact]
public void RequiredFunctionConfigureOptionsAddsTools()
{
// Arrange
var function = this.GetTestPlugin().GetFunctionsMetadata()[0].ToOpenAIFunction();
var plugin = this.GetTestPlugin();
var function = plugin.GetFunctionsMetadata()[0].ToOpenAIFunction();
var chatCompletionsOptions = new ChatCompletionsOptions();
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
var kernel = new Kernel();
kernel.Plugins.Add(plugin);

// Act
requiredFunction.ConfigureOptions(null, chatCompletionsOptions);
requiredFunction.ConfigureOptions(kernel, chatCompletionsOptions);

// Assert
Assert.NotNull(chatCompletionsOptions.ToolChoice);
Expand Down

0 comments on commit 41a85b2

Please sign in to comment.