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

.Net: Function calling abstraction #6083

Open
wants to merge 138 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
19254dc
FunctionCallContent and FunctionResultContent classes are added.
SergeyMenshykh Apr 5, 2024
0a8a616
Merge with latest main
SergeyMenshykh Apr 5, 2024
6a03050
Unnecessary warning suppressions removed
SergeyMenshykh Apr 5, 2024
a23f5e7
Fix funciton calling example
SergeyMenshykh Apr 8, 2024
a05d88d
Fix for unit tests
SergeyMenshykh Apr 8, 2024
704111c
FunctionCallContent.FunctionCall property removed.
SergeyMenshykh Apr 8, 2024
3267573
Adding FunctionResultContent items to the chat message for auto funct…
SergeyMenshykh Apr 8, 2024
79df688
Update dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs
SergeyMenshykh Apr 8, 2024
88b1f03
Update dotnet/src/InternalUtilities/src/System/IListExtensions.cs
SergeyMenshykh Apr 8, 2024
e5c327b
Update dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCa…
SergeyMenshykh Apr 8, 2024
b732256
Update dotnet/src/SemanticKernel.Abstractions/Contents/FunctionResult…
SergeyMenshykh Apr 8, 2024
8823ebd
A few minor improvments.
SergeyMenshykh Apr 8, 2024
e06c070
Merge branch 'function-call-content-types' of https://github.com/Serg…
SergeyMenshykh Apr 8, 2024
ddbb7c2
Update dotnet/src/InternalUtilities/src/System/IListExtensions.cs
SergeyMenshykh Apr 8, 2024
8569dc4
Debug.Assert instead of null checks.
SergeyMenshykh Apr 8, 2024
145968f
New ChatHsitory.AddMessage method removed.
SergeyMenshykh Apr 8, 2024
b996eb4
New ChatMessageContent.GetFunctionCalls method removed.
SergeyMenshykh Apr 8, 2024
28c8ac3
The fully qualified name parsing logic is encapsulated in the FullyQu…
SergeyMenshykh Apr 8, 2024
397795b
The `FunctionResultContent.Result` property setter is removed.
SergeyMenshykh Apr 8, 2024
f9c493d
Default null value of the `executionSettings` parameter is removed fo…
SergeyMenshykh Apr 8, 2024
900e213
Merge branch 'main' into function-call-content-types
crickman Apr 8, 2024
795c197
The unnecessary check for the result of the OfType method call is rem…
SergeyMenshykh Apr 8, 2024
9b952c0
Merge branch 'function-call-content-types' of https://github.com/Serg…
SergeyMenshykh Apr 8, 2024
055a194
The `FunctionCallContent.GetFullyQualifiedName` method is moved to th…
SergeyMenshykh Apr 8, 2024
cab73c5
Logging function parameters deserialization failure
SergeyMenshykh Apr 9, 2024
f6d3e96
The ChatHistory.AddMessage method usage is replaced by ChatHistory.Ad…
SergeyMenshykh Apr 9, 2024
ef6a55c
ChatHistory.AddMessage method usage is replaced by the ChatHistory.Ad…
SergeyMenshykh Apr 9, 2024
636a22e
non-existing project is removed
SergeyMenshykh Apr 9, 2024
b377b50
Fix for unit tests
SergeyMenshykh Apr 9, 2024
ffb8467
Merge branch 'main' into function-call-content-types
SergeyMenshykh Apr 9, 2024
c29954d
FunctionCallContent class is renamed to FunctionCallRequestContent
SergeyMenshykh Apr 9, 2024
d555b29
FunctionResultContent class is renamed to FunctionCallResultContent
SergeyMenshykh Apr 9, 2024
8e2ae55
More efficient handling of the FunctionCallResultContent items
SergeyMenshykh Apr 9, 2024
b66874f
Preserving function call request argument deserialization exception t…
SergeyMenshykh Apr 10, 2024
d015196
ClientCore refactored not to add funciton calls and function results …
SergeyMenshykh Apr 10, 2024
f9aed8d
Changes of the OpenAIChatMessageContent class are rolled back because…
SergeyMenshykh Apr 10, 2024
4bdf395
1. More optimal deduplication mechanism for function call requests ad…
SergeyMenshykh Apr 10, 2024
e53dd29
To convenience methods `FunctionCallResultContent.ToChatMessage` and …
SergeyMenshykh Apr 10, 2024
4dbdb2f
Merge with latest main
SergeyMenshykh Apr 10, 2024
b3d3518
Fix for test coverage and ignoring serialization of null properties
SergeyMenshykh Apr 10, 2024
1a29be6
Merge branch 'main' into function-call-content-types
SergeyMenshykh Apr 10, 2024
a128df2
Merge branch 'main' into function-call-content-types
crickman Apr 12, 2024
c179a6f
Simulated function example added + integration tests updated to use n…
SergeyMenshykh Apr 12, 2024
642f4f0
Merge branch 'function-call-content-types' of https://github.com/Serg…
SergeyMenshykh Apr 12, 2024
2e9efa9
Update dotnet/src/SemanticKernel.UnitTests/Utilities/FunctionNameTest…
SergeyMenshykh Apr 15, 2024
cd607bc
Update dotnet/src/SemanticKernel.UnitTests/Contents/FunctionCallResul…
SergeyMenshykh Apr 15, 2024
db1b353
Update dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ChatCompleti…
SergeyMenshykh Apr 15, 2024
2c4e482
Update dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs
SergeyMenshykh Apr 15, 2024
29b8d41
Update dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ChatCompleti…
SergeyMenshykh Apr 15, 2024
e724c98
Merge with latest main.
SergeyMenshykh Apr 15, 2024
69a8437
Fix to support non-string funciton arguments in additiojn to string o…
SergeyMenshykh Apr 15, 2024
43f1976
Merge branch 'main' into function-call-content-types
SergeyMenshykh Apr 15, 2024
f5ab032
POC with polymorphic deserialization of tool behaviors and function c…
SergeyMenshykh May 1, 2024
ac7bec9
POC for polymorphic deserialization of tool behaviors and function ca…
SergeyMenshykh May 1, 2024
0c088d8
Old example removed
SergeyMenshykh May 1, 2024
09feb8d
ConfigureFunctionCallingOptions method moved down to simplify review
SergeyMenshykh May 1, 2024
ebabb7e
renaming
SergeyMenshykh May 1, 2024
15e98e3
Solution item name change
SergeyMenshykh May 1, 2024
78c59ff
Breaking glass scenario is desabled
SergeyMenshykh May 2, 2024
fb39b44
The PromptExecutionSettings.ToolBehaviors property has been renamed t…
SergeyMenshykh May 3, 2024
7cbaaf5
fix: remove function call behavior and use function call choice instead
SergeyMenshykh May 8, 2024
afaf066
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 8, 2024
3188795
cleanup
SergeyMenshykh May 8, 2024
47da286
fix: Use the 'type' discriminator for polymorphic deserialization fro…
SergeyMenshykh May 8, 2024
7cd1685
small improvments
SergeyMenshykh May 8, 2024
7beccc0
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh May 8, 2024
6698219
* Add XML comments to classes and their members.
SergeyMenshykh May 9, 2024
90cc4ea
Merge with latest main
SergeyMenshykh May 9, 2024
827e50f
* Rename Alias const to TypeDiscriminator
SergeyMenshykh May 9, 2024
348ecde
* GetConfiguration methods refactored to handle all cases
SergeyMenshykh May 9, 2024
fa5525e
a few small fixes
SergeyMenshykh May 9, 2024
019f418
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 9, 2024
399f0a3
small fixes + first portion of unit tests
SergeyMenshykh May 9, 2024
96465da
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh May 9, 2024
b6b5b03
fix: Address PR feedback
SergeyMenshykh May 10, 2024
e3e82b8
fix: improve exception message
SergeyMenshykh May 10, 2024
58a5cb5
* Add tests for auto/required/none function choice behaviors
SergeyMenshykh May 10, 2024
581765f
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 10, 2024
79b068a
fix: add tests to check that non-kernel funcitions can be used for ma…
SergeyMenshykh May 10, 2024
13a5515
Add unit tests for {Azure}OpenAI chat completion services.
SergeyMenshykh May 10, 2024
3ef3816
Merge with latest main
SergeyMenshykh May 13, 2024
c9ca569
fix merge issues
SergeyMenshykh May 13, 2024
af08e8b
provide reason for function check in auto-invocation mode.
SergeyMenshykh May 13, 2024
bc146a8
1. Initialize Functions collection by functions supplied via construc…
SergeyMenshykh May 14, 2024
04005b7
fix compilation warnings
SergeyMenshykh May 14, 2024
6fe921f
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 14, 2024
b986dfe
add unit tests for yaml and json converters
SergeyMenshykh May 14, 2024
f61f00d
1. Add function choice to function choice behavior config
SergeyMenshykh May 14, 2024
81d459f
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 15, 2024
f280a7e
add support for functions to the none funciton choice behavior
SergeyMenshykh May 15, 2024
e85dfe4
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 15, 2024
a514842
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 15, 2024
4b5148b
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 15, 2024
e6e6009
remove MaximumAutoInvokeAttempts and MaximumUseAttempts properties fr…
SergeyMenshykh May 16, 2024
4181b82
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh May 16, 2024
19bda55
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 16, 2024
73e5bc5
remove FunctionChoiceBehaviorConfiguration.MaximumAutoInvokeAttempts …
SergeyMenshykh May 16, 2024
025d893
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh May 16, 2024
4284e26
remove FunctionChoiceBehaviorConfiguration.MaximumUseAttempts property
SergeyMenshykh May 16, 2024
5a25659
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 16, 2024
c3b8f18
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 17, 2024
5e7eebb
Merge with latest
SergeyMenshykh May 30, 2024
a2d2bd3
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 30, 2024
2bbfaf1
fix formatting warning
SergeyMenshykh May 30, 2024
3e21fc5
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh May 30, 2024
2e04fcd
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 30, 2024
a3527d7
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh May 31, 2024
162a948
Update dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehavi…
SergeyMenshykh May 31, 2024
63619a8
address PR comments
SergeyMenshykh May 31, 2024
d59bf15
merge with latest
SergeyMenshykh May 31, 2024
0eb5cd9
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 5, 2024
960176b
fix: put more detailed failure response to code comment.
SergeyMenshykh Jun 5, 2024
b3fc4db
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh Jun 5, 2024
21e2f49
fix: remove the "FunctionChoice" suffix from the `FunctionChoiceBehav…
SergeyMenshykh Jun 5, 2024
1f13f35
fix: address PR comments
SergeyMenshykh Jun 10, 2024
b4fc6ed
Revert "fix: address PR comments"
SergeyMenshykh Jun 11, 2024
9e433dd
fix: restrict constraining extensibility of choice behavior-related c…
SergeyMenshykh Jun 11, 2024
c9af87d
Merge with latest main
SergeyMenshykh Jun 11, 2024
058895a
fix: fix compilation warnings
SergeyMenshykh Jun 11, 2024
d1af7ff
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 11, 2024
a0de345
fix: address PR comments
SergeyMenshykh Jun 11, 2024
7df6570
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh Jun 11, 2024
b90d1a1
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 11, 2024
abed585
fix: IDE0005 formatting issue
SergeyMenshykh Jun 11, 2024
c629d70
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh Jun 11, 2024
955777b
Update dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehavi…
SergeyMenshykh Jun 11, 2024
85a4fce
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 11, 2024
12df1e0
fix: fix compilation warning
SergeyMenshykh Jun 11, 2024
41448b5
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh Jun 11, 2024
4d748b9
fix: restoring lost logic in OpenAI connector to disable function cal…
SergeyMenshykh Jun 12, 2024
25309ba
Merge remote-tracking branch 'origin/main' into function-calling-abst…
SergeyMenshykh Jun 14, 2024
7530cef
fix: fix unit test and samples
SergeyMenshykh Jun 14, 2024
4d89a50
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 14, 2024
1801926
feat: add options to funciton choice behavior classes to supply diffe…
SergeyMenshykh Jun 17, 2024
138db30
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 17, 2024
e892848
fix: remove duplication of function resolution functionality.
SergeyMenshykh Jun 17, 2024
6cad710
Merge branch 'function-calling-abstraction-poc' of https://github.com…
SergeyMenshykh Jun 17, 2024
2680bb6
Merge branch 'main' into function-calling-abstraction-poc
SergeyMenshykh Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
211 changes: 142 additions & 69 deletions dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ public override PromptExecutionSettings Clone()
ResponseFormat = this.ResponseFormat,
TokenSelectionBiases = this.TokenSelectionBiases is not null ? new Dictionary<int, int>(this.TokenSelectionBiases) : null,
ToolCallBehavior = this.ToolCallBehavior,
FunctionChoiceBehavior = this.FunctionChoiceBehavior,
User = this.User,
ChatSystemPrompt = this.ChatSystemPrompt
};
Expand Down Expand Up @@ -330,6 +331,8 @@ public static OpenAIPromptExecutionSettings FromExecutionSettings(PromptExecutio
var openAIExecutionSettings = JsonSerializer.Deserialize<OpenAIPromptExecutionSettings>(json, JsonOptionsCache.ReadPermissive);
if (openAIExecutionSettings is not null)
{
// Restores the original function choice behavior that lost internal state(list of functions) during serialization/deserialization process.
openAIExecutionSettings.FunctionChoiceBehavior = executionSettings.FunctionChoiceBehavior;
return openAIExecutionSettings;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,102 @@ public async Task FunctionResultsCanBeProvidedToLLMAsManyResultsInOneChatMessage
Assert.Equal("2", assistantMessage2.GetProperty("tool_call_id").GetString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingAutoFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.AddFromFunctions("TimePlugin", [
KernelFunctionFactory.CreateFromMethod(() => { }, "Date"),
KernelFunctionFactory.CreateFromMethod(() => { }, "Now")
]);

var chatCompletion = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);

this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.AutoFunctionChoice() };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContents[0]!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(2, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("TimePlugin-Now", optionsJson.GetProperty("tools")[1].GetProperty("function").GetProperty("name").GetString());

Assert.Equal("auto", optionsJson.GetProperty("tool_choice").ToString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingRequiredFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.AddFromFunctions("TimePlugin", [
KernelFunctionFactory.CreateFromMethod(() => { }, "Date"),
]);

var chatCompletion = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);

this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.RequiredFunctionChoice() };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContents[0]!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(1, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tool_choice").GetProperty("function").GetProperty("name").ToString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingNoneFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.AddFromFunctions("TimePlugin", [
KernelFunctionFactory.CreateFromMethod(() => { }, "Date"),
]);

var chatCompletion = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);

this._messageHandlerStub.ResponsesToReturn.Add(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(OpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
});

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.None };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContents[0]!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(1, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("NonInvocableTool", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("none", optionsJson.GetProperty("tool_choice").ToString());
}

public void Dispose()
{
this._httpClient.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
Expand All @@ -27,7 +28,8 @@ public sealed class OpenAIChatCompletionServiceTests : IDisposable
{
private readonly HttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly OpenAIFunction _timepluginDate, _timepluginNow;
private readonly KernelPlugin _plugin;
private readonly KernelFunction _timepluginDate, _timepluginNow;
private readonly OpenAIPromptExecutionSettings _executionSettings;
private readonly Mock<ILoggerFactory> _mockLoggerFactory;

Expand All @@ -37,18 +39,21 @@ public OpenAIChatCompletionServiceTests()
this._httpClient = new HttpClient(this._messageHandlerStub, false);
this._mockLoggerFactory = new Mock<ILoggerFactory>();

IList<KernelFunctionMetadata> functions = KernelPluginFactory.CreateFromFunctions("TimePlugin", new[]
this._plugin = KernelPluginFactory.CreateFromFunctions("TimePlugin", new[]
{
KernelFunctionFactory.CreateFromMethod((string? format = null) => DateTime.Now.Date.ToString(format, CultureInfo.InvariantCulture), "Date", "TimePlugin.Date"),
KernelFunctionFactory.CreateFromMethod((string? format = null) => DateTime.Now.ToString(format, CultureInfo.InvariantCulture), "Now", "TimePlugin.Now"),
}).GetFunctionsMetadata();
});

this._timepluginDate = functions[0].ToOpenAIFunction();
this._timepluginNow = functions[1].ToOpenAIFunction();
this._timepluginDate = this._plugin.ElementAt(0);
this._timepluginNow = this._plugin.ElementAt(1);

this._executionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.EnableFunctions([this._timepluginDate, this._timepluginNow])
ToolCallBehavior = ToolCallBehavior.EnableFunctions([
this._timepluginDate.Metadata.ToOpenAIFunction(),
this._timepluginNow.Metadata.ToOpenAIFunction()
])
};
}

Expand Down Expand Up @@ -161,7 +166,7 @@ public async Task ItCreatesCorrectFunctionToolCallsWhenUsingNowAsync()
var chatCompletion = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient);
this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{ Content = new StringContent(ChatCompletionResponse) };
this._executionSettings.ToolCallBehavior = ToolCallBehavior.RequireFunction(this._timepluginNow);
this._executionSettings.ToolCallBehavior = ToolCallBehavior.RequireFunction(this._timepluginNow.Metadata.ToOpenAIFunction());

// Act
await chatCompletion.GetChatMessageContentsAsync([], this._executionSettings);
Expand Down Expand Up @@ -587,6 +592,95 @@ public async Task FunctionResultsCanBeProvidedToLLMAsManyResultsInOneChatMessage
Assert.Equal("2", assistantMessage2.GetProperty("tool_call_id").GetString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingAutoFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.Add(this._plugin);

var chatCompletion = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient);

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(ChatCompletionResponse)
};

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.AutoFunctionChoice() };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(2, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("TimePlugin-Now", optionsJson.GetProperty("tools")[1].GetProperty("function").GetProperty("name").GetString());

Assert.Equal("auto", optionsJson.GetProperty("tool_choice").ToString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingRequiredFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.AddFromFunctions("TimePlugin", [this._timepluginDate]);

var chatCompletion = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient);

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(ChatCompletionResponse)
};

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.RequiredFunctionChoice() };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(1, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("TimePlugin-Date", optionsJson.GetProperty("tool_choice").GetProperty("function").GetProperty("name").ToString());
}

[Fact]
public async Task ItCreatesCorrectFunctionToolCallsWhenUsingNoneFunctionChoiceBehaviorAsync()
{
// Arrange
var kernel = new Kernel();
kernel.Plugins.AddFromFunctions("TimePlugin", [this._timepluginDate]);

var chatCompletion = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient);

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(ChatCompletionResponse)
};

var executionSettings = new OpenAIPromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.None };

// Act
await chatCompletion.GetChatMessageContentsAsync([], executionSettings, kernel);

// Assert
var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
Assert.NotNull(actualRequestContent);

var optionsJson = JsonSerializer.Deserialize<JsonElement>(actualRequestContent);
Assert.Equal(1, optionsJson.GetProperty("tools").GetArrayLength());
Assert.Equal("NonInvocableTool", optionsJson.GetProperty("tools")[0].GetProperty("function").GetProperty("name").GetString());
Assert.Equal("none", optionsJson.GetProperty("tool_choice").ToString());
}

public void Dispose()
{
this._httpClient.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,22 @@ public void FromExecutionSettingsWithDataDoesNotIncludeEmptyStopSequences()
Assert.Null(executionSettingsWithData.StopSequences);
}

[Fact]
public void ItRestoresOriginalFunctionChoiceBehavior()
{
// Arrange
var functionChoiceBehavior = FunctionChoiceBehavior.None;

var originalExecutionSettings = new PromptExecutionSettings();
originalExecutionSettings.FunctionChoiceBehavior = functionChoiceBehavior;

// Act
var result = OpenAIPromptExecutionSettings.FromExecutionSettings(originalExecutionSettings);

// Assert
Assert.Equal(functionChoiceBehavior, result.FunctionChoiceBehavior);
}

private static void AssertExecutionSettings(OpenAIPromptExecutionSettings executionSettings)
{
Assert.NotNull(executionSettings);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Linq;
using Microsoft.SemanticKernel;
using Xunit;

Expand All @@ -18,9 +19,56 @@ public void ItShouldCreatePromptFunctionConfigFromMarkdown()
Assert.NotNull(model);
Assert.Equal("TellMeAbout", model.Name);
Assert.Equal("Hello AI, tell me about {{$input}}", model.Template);
Assert.Equal(2, model.ExecutionSettings.Count);
Assert.Equal(3, model.ExecutionSettings.Count);
Assert.Equal("gpt4", model.ExecutionSettings["service1"].ModelId);
Assert.Equal("gpt3.5", model.ExecutionSettings["service2"].ModelId);
Assert.Equal("gpt3.5-turbo", model.ExecutionSettings["service3"].ModelId);
}

[Fact]
public void ItShouldInitializeFunctionChoiceBehaviorsFromMarkdown()
{
// Arrange
var kernel = new Kernel();

// Act
var function = KernelFunctionMarkdown.CreateFromPromptMarkdown(Markdown, "TellMeAbout");

// Assert
Assert.NotNull(function);
Assert.NotEmpty(function.ExecutionSettings);

Assert.Equal(3, function.ExecutionSettings.Count);

// AutoFunctionCallChoice for service1
var service1ExecutionSettings = function.ExecutionSettings["service1"];
Assert.NotNull(service1ExecutionSettings);

var autoFunctionChoiceBehavior = service1ExecutionSettings.FunctionChoiceBehavior as AutoFunctionChoiceBehavior;
Assert.NotNull(autoFunctionChoiceBehavior);

Assert.NotNull(autoFunctionChoiceBehavior.Functions);
Assert.Single(autoFunctionChoiceBehavior.Functions);
Assert.Equal("p1.f1", autoFunctionChoiceBehavior.Functions.First());
Assert.Equal(8, autoFunctionChoiceBehavior.MaximumAutoInvokeAttempts);

// RequiredFunctionCallChoice for service2
var service2ExecutionSettings = function.ExecutionSettings["service2"];
Assert.NotNull(service2ExecutionSettings);

var requiredFunctionChoiceBehavior = service2ExecutionSettings.FunctionChoiceBehavior as RequiredFunctionChoiceBehavior;
Assert.NotNull(requiredFunctionChoiceBehavior);
Assert.NotNull(requiredFunctionChoiceBehavior.Functions);
Assert.Single(requiredFunctionChoiceBehavior.Functions);
Assert.Equal("p1.f1", requiredFunctionChoiceBehavior.Functions.First());
Assert.Equal(2, requiredFunctionChoiceBehavior.MaximumUseAttempts);

// NoneFunctionCallChoice for service3
var service3ExecutionSettings = function.ExecutionSettings["service3"];
Assert.NotNull(service3ExecutionSettings);

var noneFunctionChoiceBehavior = service3ExecutionSettings.FunctionChoiceBehavior as NoneFunctionChoiceBehavior;
Assert.NotNull(noneFunctionChoiceBehavior);
}

[Fact]
Expand All @@ -47,7 +95,12 @@ public void ItShouldCreatePromptFunctionFromMarkdown()
{
"service1" : {
"model_id": "gpt4",
"temperature": 0.7
"temperature": 0.7,
"function_choice_behavior": {
"type": "auto",
"functions": ["p1.f1"],
"maximumAutoInvokeAttempts": 8
}
}
}
```
Expand All @@ -56,7 +109,24 @@ public void ItShouldCreatePromptFunctionFromMarkdown()
{
"service2" : {
"model_id": "gpt3.5",
"temperature": 0.8
"temperature": 0.8,
"function_choice_behavior": {
"type": "required",
"functions": ["p1.f1"],
"maximumUseAttempts": 2
}
}
}
```
These are AI execution settings as well
```sk.execution_settings
{
"service3" : {
"model_id": "gpt3.5-turbo",
"temperature": 0.8,
"function_choice_behavior": {
"type": "none"
}
}
}
```
Expand Down