Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ public void AutoInvokeKernelFunctionsReturnsCorrectKernelFunctionsInstance()
Assert.Equal(DefaultMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void CreateAutoInvokeKernelFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 15;
var behavior = GeminiToolCallBehavior.CreateAutoInvokeKernelFunctions(CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<GeminiToolCallBehavior.KernelFunctions>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void EnableFunctionsReturnsEnabledFunctionsInstance()
{
Expand All @@ -50,6 +62,20 @@ public void EnableFunctionsReturnsEnabledFunctionsInstance()
Assert.IsType<GeminiToolCallBehavior.EnabledFunctions>(behavior);
}

[Fact]
public void EnableFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 7;
List<GeminiFunction> functions =
[new GeminiFunction("Plugin", "Function", "description", [], null)];
var behavior = GeminiToolCallBehavior.EnableFunctions(functions, autoInvoke: true, CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<GeminiToolCallBehavior.EnabledFunctions>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void KernelFunctionsConfigureGeminiRequestWithNullKernelDoesNotAddTools()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ public abstract class GeminiToolCallBehavior
/// </remarks>
public static GeminiToolCallBehavior AutoInvokeKernelFunctions => new KernelFunctions(autoInvoke: true);

/// <summary>
/// Creates an instance that will both provide all of the <see cref="Kernel"/>'s plugins' function information
/// to the model and attempt to automatically handle any function call requests with the specified maximum number of attempts.
/// </summary>
/// <param name="maximumAutoInvokeAttempts">The maximum number of auto-invoke attempts allowed.</param>
/// <remarks>
/// When successful, tool call requests from the model become an implementation detail, with the service
/// handling invoking any requested functions and supplying the results back to the model.
/// If no <see cref="Kernel"/> is available, no function information will be provided to the model.
/// </remarks>
/// <returns>A <see cref="GeminiToolCallBehavior"/> instance configured with the specified maximum auto-invoke attempts.</returns>
public static GeminiToolCallBehavior CreateAutoInvokeKernelFunctions(int maximumAutoInvokeAttempts)
{
return new KernelFunctions(autoInvoke: true, maximumAutoInvokeAttempts);
}

/// <summary>Gets an instance that will provide the specified list of functions to the model.</summary>
/// <param name="functions">The functions that should be made available to the model.</param>
/// <param name="autoInvoke">true to attempt to automatically handle function call requests; otherwise, false.</param>
Expand All @@ -67,12 +83,32 @@ public static GeminiToolCallBehavior EnableFunctions(IEnumerable<GeminiFunction>
return new EnabledFunctions(functions, autoInvoke);
}

/// <summary>Gets an instance that will provide the specified list of functions to the model.</summary>
/// <param name="functions">The functions that should be made available to the model.</param>
/// <param name="autoInvoke">true to attempt to automatically handle function call requests; otherwise, false.</param>
/// <param name="maximumAutoInvokeAttempts">The maximum number of auto-invoke attempts allowed.</param>
/// <returns>
/// The <see cref="GeminiToolCallBehavior"/> that may be set into <see cref="GeminiToolCallBehavior"/>
/// to indicate that the specified functions should be made available to the model.
/// </returns>
public static GeminiToolCallBehavior EnableFunctions(IEnumerable<GeminiFunction> functions, bool autoInvoke, int maximumAutoInvokeAttempts)
{
Verify.NotNull(functions);
return new EnabledFunctions(functions, autoInvoke, maximumAutoInvokeAttempts);
}

/// <summary>Initializes the instance; prevents external instantiation.</summary>
private GeminiToolCallBehavior(bool autoInvoke)
{
this.MaximumAutoInvokeAttempts = autoInvoke ? DefaultMaximumAutoInvokeAttempts : 0;
}

/// <summary>Initializes the instance; prevents external instantiation.</summary>
private GeminiToolCallBehavior(bool autoInvoke, int maximumAutoInvokeAttempts)
{
this.MaximumAutoInvokeAttempts = autoInvoke ? maximumAutoInvokeAttempts : 0;
}

/// <summary>Gets how many requests are part of a single interaction should include this tool in the request.</summary>
/// <remarks>
/// This should be greater than or equal to <see cref="MaximumAutoInvokeAttempts"/>. It defaults to <see cref="int.MaxValue"/>.
Expand Down Expand Up @@ -114,6 +150,8 @@ internal sealed class KernelFunctions : GeminiToolCallBehavior
{
internal KernelFunctions(bool autoInvoke) : base(autoInvoke) { }

internal KernelFunctions(bool autoInvoke, int maximumAutoInvokeAttempts) : base(autoInvoke, maximumAutoInvokeAttempts) { }

public override string ToString() => $"{nameof(KernelFunctions)}(autoInvoke:{this.MaximumAutoInvokeAttempts != 0})";

internal override void ConfigureGeminiRequest(Kernel? kernel, GeminiRequest request)
Expand All @@ -137,9 +175,19 @@ internal override void ConfigureGeminiRequest(Kernel? kernel, GeminiRequest requ
/// <summary>
/// Represents a <see cref="GeminiToolCallBehavior"/> that provides a specified list of functions to the model.
/// </summary>
internal sealed class EnabledFunctions(IEnumerable<GeminiFunction> functions, bool autoInvoke) : GeminiToolCallBehavior(autoInvoke)
internal sealed class EnabledFunctions : GeminiToolCallBehavior
{
private readonly GeminiFunction[] _functions = functions.ToArray();
private readonly GeminiFunction[] _functions;

internal EnabledFunctions(IEnumerable<GeminiFunction> functions, bool autoInvoke) : base(autoInvoke)
{
this._functions = functions.ToArray();
}

internal EnabledFunctions(IEnumerable<GeminiFunction> functions, bool autoInvoke, int maximumAutoInvokeAttempts) : base(autoInvoke, maximumAutoInvokeAttempts)
{
this._functions = functions.ToArray();
}

public override string ToString() =>
$"{nameof(EnabledFunctions)}(autoInvoke:{this.MaximumAutoInvokeAttempts != 0}): " +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.MistralAI;
using Xunit;
using static Microsoft.SemanticKernel.Connectors.MistralAI.MistralAIToolCallBehavior;

namespace SemanticKernel.Connectors.MistralAI.UnitTests;

/// <summary>
/// Unit tests for <see cref="MistralAIToolCallBehavior"/>
/// </summary>
public sealed class MistralAIToolCallBehaviorTests
{
[Fact]
public void EnableKernelFunctionsReturnsCorrectKernelFunctionsInstance()
{
// Arrange & Act
var behavior = MistralAIToolCallBehavior.EnableKernelFunctions;

// Assert
Assert.IsType<KernelFunctions>(behavior);
Assert.Equal(0, behavior.MaximumAutoInvokeAttempts);
Assert.Equal($"{nameof(KernelFunctions)}(autoInvoke:{behavior.MaximumAutoInvokeAttempts != 0})", behavior.ToString());
}

[Fact]
public void AutoInvokeKernelFunctionsReturnsCorrectKernelFunctionsInstance()
{
// Arrange & Act
const int DefaultMaximumAutoInvokeAttempts = 5;
var behavior = MistralAIToolCallBehavior.AutoInvokeKernelFunctions;

// Assert
Assert.IsType<KernelFunctions>(behavior);
Assert.Equal(DefaultMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void CreateAutoInvokeKernelFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 10;
var behavior = MistralAIToolCallBehavior.CreateAutoInvokeKernelFunctions(CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<KernelFunctions>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void RequiredFunctionsReturnsAnyFunctionInstance()
{
// Arrange & Act
var function = KernelFunctionFactory.CreateFromMethod(() => "Result", "MyFunction");
List<KernelFunction> functions = [function];
var behavior = MistralAIToolCallBehavior.RequiredFunctions(functions);

// Assert
Assert.IsType<AnyFunction>(behavior);
Assert.Contains($"{nameof(AnyFunction)}(autoInvoke:{behavior.MaximumAutoInvokeAttempts != 0})", behavior.ToString());
}

[Fact]
public void RequiredFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 3;
var function = KernelFunctionFactory.CreateFromMethod(() => "Result", "MyFunction");
List<KernelFunction> functions = [function];
var behavior = MistralAIToolCallBehavior.RequiredFunctions(functions, autoInvoke: true, CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<AnyFunction>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ public abstract class MistralAIToolCallBehavior
/// </remarks>
public static MistralAIToolCallBehavior AutoInvokeKernelFunctions { get; } = new KernelFunctions(autoInvoke: true);

/// <summary>
/// Creates an instance that will both provide all of the <see cref="Kernel"/>'s plugins' function information
/// to the model and attempt to automatically handle any function call requests with the specified maximum number of attempts.
/// </summary>
/// <param name="maximumAutoInvokeAttempts">The maximum number of auto-invoke attempts allowed.</param>
/// <remarks>
/// When successful, tool call requests from the model become an implementation detail, with the service
/// handling invoking any requested functions and supplying the results back to the model.
/// If no <see cref="Kernel"/> is available, no function information will be provided to the model.
/// </remarks>
/// <returns>A <see cref="MistralAIToolCallBehavior"/> instance configured with the specified maximum auto-invoke attempts.</returns>
public static MistralAIToolCallBehavior CreateAutoInvokeKernelFunctions(int maximumAutoInvokeAttempts)
{
return new KernelFunctions(autoInvoke: true, maximumAutoInvokeAttempts);
}

/// <summary>Gets an instance that will provide the specified list of functions to the model.</summary>
/// <param name="functions">The functions that should be made available to the model.</param>
/// <param name="autoInvoke">true to attempt to automatically handle function call requests; otherwise, false.</param>
Expand All @@ -70,6 +86,21 @@ public static MistralAIToolCallBehavior RequiredFunctions(IEnumerable<KernelFunc
return new AnyFunction(functions, autoInvoke);
}

/// <summary>Gets an instance that will provide the specified list of functions to the model.</summary>
/// <param name="functions">The functions that should be made available to the model.</param>
/// <param name="autoInvoke">true to attempt to automatically handle function call requests; otherwise, false.</param>
/// <param name="maximumAutoInvokeAttempts">The maximum number of auto-invoke attempts allowed.</param>
/// <returns>
/// The <see cref="MistralAIToolCallBehavior"/> that may be set into <see cref="MistralAIPromptExecutionSettings.ToolCallBehavior"/>
/// to indicate that the specified functions should be made available to the model.
/// The model is forced to call a function from the list of functions provided.
/// </returns>
public static MistralAIToolCallBehavior RequiredFunctions(IEnumerable<KernelFunction> functions, bool autoInvoke, int maximumAutoInvokeAttempts)
{
Verify.NotNull(functions);
return new AnyFunction(functions, autoInvoke, maximumAutoInvokeAttempts);
}

/// <summary>
/// Gets an instance that will both provide all of the <see cref="Kernel"/>'s plugins' function information
/// to the model but not any function call requests.
Expand All @@ -87,6 +118,12 @@ private MistralAIToolCallBehavior(bool autoInvoke)
this.MaximumAutoInvokeAttempts = autoInvoke ? DefaultMaximumAutoInvokeAttempts : 0;
}

/// <summary>Initializes the instance; prevents external instantiation.</summary>
private MistralAIToolCallBehavior(bool autoInvoke, int maximumAutoInvokeAttempts)
{
this.MaximumAutoInvokeAttempts = autoInvoke ? maximumAutoInvokeAttempts : 0;
}

/// <summary>
/// Options to control tool call result serialization behavior.
/// </summary>
Expand Down Expand Up @@ -126,6 +163,8 @@ internal sealed class KernelFunctions : MistralAIToolCallBehavior
{
internal KernelFunctions(bool autoInvoke) : base(autoInvoke) { }

internal KernelFunctions(bool autoInvoke, int maximumAutoInvokeAttempts) : base(autoInvoke, maximumAutoInvokeAttempts) { }

public override string ToString() => $"{nameof(KernelFunctions)}(autoInvoke:{this.MaximumAutoInvokeAttempts != 0})";

internal IEnumerable<KernelFunctionMetadata>? GetFunctionsMetadata(Kernel? kernel)
Expand Down Expand Up @@ -167,9 +206,19 @@ internal override void ConfigureRequest(Kernel? kernel, ChatCompletionRequest re
/// <summary>
/// Represents a <see cref="MistralAIToolCallBehavior"/> that provides a specified list of functions to the model.
/// </summary>
internal sealed class AnyFunction(IEnumerable<KernelFunction> functions, bool autoInvoke) : MistralAIToolCallBehavior(autoInvoke)
internal sealed class AnyFunction : MistralAIToolCallBehavior
{
private readonly IEnumerable<KernelFunctionMetadata>? _kernelFunctionMetadata = functions.Select(f => f.Metadata);
private readonly IEnumerable<KernelFunctionMetadata>? _kernelFunctionMetadata;

internal AnyFunction(IEnumerable<KernelFunction> functions, bool autoInvoke) : base(autoInvoke)
{
this._kernelFunctionMetadata = functions.Select(f => f.Metadata);
}

internal AnyFunction(IEnumerable<KernelFunction> functions, bool autoInvoke, int maximumAutoInvokeAttempts) : base(autoInvoke, maximumAutoInvokeAttempts)
{
this._kernelFunctionMetadata = functions.Select(f => f.Metadata);
}

public override string ToString() => $"{nameof(AnyFunction)}(autoInvoke:{this.MaximumAutoInvokeAttempts != 0}): {string.Join(", ", this._kernelFunctionMetadata!.Select(f => f.Name))}";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ public void AutoInvokeKernelFunctionsReturnsCorrectKernelFunctionsInstance()
Assert.Equal(DefaultMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void CreateAutoInvokeKernelFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 5;
var behavior = ToolCallBehavior.CreateAutoInvokeKernelFunctions(CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<KernelFunctions>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void EnableFunctionsReturnsEnabledFunctionsInstance()
{
Expand All @@ -51,6 +63,19 @@ public void EnableFunctionsReturnsEnabledFunctionsInstance()
Assert.Contains($"{nameof(EnabledFunctions)}(autoInvoke:{behavior.MaximumAutoInvokeAttempts != 0})", behavior.ToString());
}

[Fact]
public void EnableFunctionsWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 10;
List<OpenAIFunction> functions = [new("Plugin", "Function", "description", [], null)];
var behavior = ToolCallBehavior.EnableFunctions(functions, autoInvoke: true, CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<EnabledFunctions>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void RequireFunctionReturnsRequiredFunctionInstance()
{
Expand All @@ -62,6 +87,18 @@ public void RequireFunctionReturnsRequiredFunctionInstance()
Assert.Contains($"{nameof(RequiredFunction)}(autoInvoke:{behavior.MaximumAutoInvokeAttempts != 0})", behavior.ToString());
}

[Fact]
public void RequireFunctionWithCustomMaximumReturnsCorrectInstance()
{
// Arrange & Act
const int CustomMaximumAutoInvokeAttempts = 3;
var behavior = ToolCallBehavior.RequireFunction(new("Plugin", "Function", "description", [], null), autoInvoke: true, CustomMaximumAutoInvokeAttempts);

// Assert
Assert.IsType<RequiredFunction>(behavior);
Assert.Equal(CustomMaximumAutoInvokeAttempts, behavior.MaximumAutoInvokeAttempts);
}

[Fact]
public void KernelFunctionsConfigureOptionsWithNullKernelDoesNotAddTools()
{
Expand Down
Loading
Loading