Skip to content

Commit 9bf7fae

Browse files
committed
.Net: Add RetainArgumentTypes support to FunctionCallContentBuilder
Before this patch, the `FunctionCallContentBuilder` did not support retaining the argument types. We need this to support retained argument types when not using auto invoke function choices.
1 parent 28ea2f4 commit 9bf7fae

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

dotnet/src/SemanticKernel.Abstractions/Contents/FunctionCallContentBuilder.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public sealed class FunctionCallContentBuilder
1818
private Dictionary<string, string>? _functionCallIdsByIndex = null;
1919
private Dictionary<string, string>? _functionNamesByIndex = null;
2020
private Dictionary<string, StringBuilder>? _functionArgumentBuildersByIndex = null;
21-
private readonly JsonSerializerOptions? _jsonSerializerOptions;
21+
private readonly JsonSerializerOptions? _jsonSerializerOptions = null;
22+
private readonly bool _retainArgumentTypes = false;
2223

2324
/// <summary>
2425
/// Creates a new instance of the <see cref="FunctionCallContentBuilder"/> class.
@@ -33,10 +34,12 @@ public FunctionCallContentBuilder()
3334
/// Creates a new instance of the <see cref="FunctionCallContentBuilder"/> class.
3435
/// </summary>
3536
/// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for deserializing function arguments.</param>
37+
/// <param name="retainArgumentTypes">A value indicating whether the types of function arguments provided by the AI model are retained by SK or not. By default <see langword="false"/>.</param>
3638
[Experimental("SKEXP0120")]
37-
public FunctionCallContentBuilder(JsonSerializerOptions jsonSerializerOptions)
39+
public FunctionCallContentBuilder(JsonSerializerOptions? jsonSerializerOptions = null, bool retainArgumentTypes = false)
3840
{
3941
this._jsonSerializerOptions = jsonSerializerOptions;
42+
this._retainArgumentTypes = retainArgumentTypes;
4043
}
4144

4245
/// <summary>
@@ -146,7 +149,7 @@ public IReadOnlyList<FunctionCallContent> Build()
146149
arguments = JsonSerializer.Deserialize<KernelArguments>(argumentsString);
147150
}
148151

149-
if (arguments is { Count: > 0 })
152+
if (arguments is { Count: > 0 } && !this._retainArgumentTypes)
150153
{
151154
var names = arguments.Names.ToArray();
152155
foreach (var name in names)

dotnet/src/SemanticKernel.UnitTests/Contents/FunctionCallBuilder/FunctionCallContentBuilderTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,60 @@ public void ItShouldCaptureArgumentsDeserializationException(JsonSerializerOptio
212212
Assert.NotNull(functionCall.Exception);
213213
}
214214

215+
[Theory]
216+
[InlineData(false)]
217+
[InlineData(true)]
218+
public void ItShouldRetainArgumentTypesIfSpecified(bool retain)
219+
{
220+
// Arrange
221+
var sut = new FunctionCallContentBuilder(null, retainArgumentTypes: retain);
222+
223+
// Act
224+
var update1 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: "f_101", name: null, arguments: null);
225+
sut.Append(update1);
226+
227+
var update2 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: null, name: "WeatherUtils-GetTemperature", arguments: null);
228+
sut.Append(update2);
229+
230+
var update3 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: null, name: null, arguments: "{\"city\":");
231+
sut.Append(update3);
232+
233+
var update4 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: null, name: null, arguments: "\"Seattle\",");
234+
sut.Append(update4);
235+
236+
var update5 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: null, name: null, arguments: "\"temperature\":");
237+
sut.Append(update5);
238+
239+
var update6 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 2, callId: null, name: null, arguments: "20}");
240+
sut.Append(update6);
241+
242+
var functionCalls = sut.Build();
243+
244+
// Assert
245+
var functionCall = Assert.Single(functionCalls);
246+
247+
Assert.Equal("f_101", functionCall.Id);
248+
Assert.Equal("WeatherUtils", functionCall.PluginName);
249+
Assert.Equal("GetTemperature", functionCall.FunctionName);
250+
Assert.NotNull(functionCall.Arguments);
251+
252+
if (retain)
253+
{
254+
var city = Assert.IsType<JsonElement>(functionCall.Arguments?["city"]);
255+
Assert.Equal(JsonValueKind.String, city.ValueKind);
256+
Assert.Equal("Seattle", city.GetString());
257+
258+
var temperature = Assert.IsType<JsonElement>(functionCall.Arguments?["temperature"]);
259+
Assert.Equal(JsonValueKind.Number, temperature.ValueKind);
260+
Assert.Equal(20, temperature.GetInt32());
261+
}
262+
else
263+
{
264+
Assert.Equal("Seattle", functionCall.Arguments?["city"]);
265+
Assert.Equal("20", functionCall.Arguments?["temperature"]);
266+
}
267+
}
268+
215269
private static StreamingChatMessageContent CreateStreamingContentWithFunctionCallUpdate(int choiceIndex, int functionCallIndex, string? callId, string? name, string? arguments, int requestIndex = 0)
216270
{
217271
var content = new StreamingChatMessageContent(AuthorRole.Assistant, null);

0 commit comments

Comments
 (0)