-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Fix PluginName empty when using Ollama/Gemini with AddFromObject (#13516) #13672
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,7 @@ public IReadOnlyList<FunctionCallContent> Build() | |
|
|
||
| if (this._functionNamesByIndex?.TryGetValue(functionCallIndexAndId.Key, out string? fqn) ?? false) | ||
| { | ||
| var functionFullyQualifiedName = Microsoft.SemanticKernel.FunctionName.Parse(fqn); | ||
| var functionFullyQualifiedName = ParseFullyQualifiedFunctionName(fqn); | ||
| pluginName = functionFullyQualifiedName.PluginName; | ||
| functionName = functionFullyQualifiedName.Name; | ||
| } | ||
|
|
@@ -170,6 +170,28 @@ public IReadOnlyList<FunctionCallContent> Build() | |
| /// <param name="functionCallIdsByIndex">The dictionary of function call IDs by function call index.</param> | ||
| /// <param name="functionNamesByIndex">The dictionary of function names by function call index.</param> | ||
| /// <param name="functionArgumentBuildersByIndex">The dictionary of function argument builders by function call index.</param> | ||
|
|
||
| /// <summary> | ||
| /// Parses a fully-qualified function name into plugin and function parts. | ||
| /// Tries multiple separators (".", "_", "-") since different AI connectors use different formats. | ||
| /// </summary> | ||
| private static FunctionName ParseFullyQualifiedFunctionName(string fullyQualifiedName) | ||
| { | ||
| foreach (var separator in new[] { ".", "_", "-" }) | ||
| { | ||
| if (fullyQualifiedName.Contains(separator, StringComparison.Ordinal)) | ||
| { | ||
| var parsed = FunctionName.Parse(fullyQualifiedName, separator); | ||
| if (parsed.PluginName is not null) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security: The |
||
| { | ||
| return parsed; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Including |
||
| return FunctionName.Parse(fullyQualifiedName); | ||
| } | ||
|
|
||
|
Comment on lines
+173
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Structural issue: inserting the new method here splits the XML |
||
| private static void TrackStreamingFunctionCallUpdate(StreamingFunctionCallUpdateContent update, ref Dictionary<string, string>? functionCallIdsByIndex, ref Dictionary<string, string>? functionNamesByIndex, ref Dictionary<string, StringBuilder>? functionArgumentBuildersByIndex) | ||
| { | ||
| if (update is null) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -212,6 +212,48 @@ public void ItShouldCaptureArgumentsDeserializationException(JsonSerializerOptio | |
| Assert.NotNull(functionCall.Exception); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ItShouldParseUnderscoreSeparatorForOllamaAndGeminiConnectors() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test name |
||
| { | ||
| // Arrange - Ollama/Gemini use underscore (e.g. time_ReadFile) per FullyQualifiedAIFunction | ||
| var sut = new FunctionCallContentBuilder(); | ||
|
|
||
| // Act | ||
| var update1 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 0, callId: "call_1", name: "time_ReadFile", arguments: null); | ||
| sut.Append(update1); | ||
|
|
||
| var update2 = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 0, callId: null, name: null, arguments: "{\"filePath\":\"d:/test.txt\"}"); | ||
| sut.Append(update2); | ||
|
|
||
| var functionCalls = sut.Build(); | ||
|
|
||
| // Assert | ||
| var functionCall = Assert.Single(functionCalls); | ||
| Assert.Equal("call_1", functionCall.Id); | ||
| Assert.Equal("time", functionCall.PluginName); | ||
| Assert.Equal("ReadFile", functionCall.FunctionName); | ||
| Assert.NotNull(functionCall.Arguments); | ||
| Assert.Equal("d:/test.txt", functionCall.Arguments["filePath"]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ItShouldParseDotSeparatorForFunctionChoiceBehavior() | ||
| { | ||
| // Arrange - FunctionChoiceBehavior uses dot (e.g. time.ReadFile) | ||
| var sut = new FunctionCallContentBuilder(); | ||
|
|
||
| // Act | ||
| var update = CreateStreamingContentWithFunctionCallUpdate(choiceIndex: 1, functionCallIndex: 0, callId: "call_1", name: "time.ReadFile", arguments: null); | ||
| sut.Append(update); | ||
|
|
||
| var functionCalls = sut.Build(); | ||
|
|
||
| // Assert | ||
| var functionCall = Assert.Single(functionCalls); | ||
| Assert.Equal("time", functionCall.PluginName); | ||
| Assert.Equal("ReadFile", functionCall.FunctionName); | ||
| } | ||
|
|
||
| private static StreamingChatMessageContent CreateStreamingContentWithFunctionCallUpdate(int choiceIndex, int functionCallIndex, string? callId, string? name, string? arguments, int requestIndex = 0) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing test coverage: (1) the hyphen separator path is untested—add a test with e.g. |
||
| { | ||
| var content = new StreamingChatMessageContent(AuthorRole.Assistant, null); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: This separator-guessing loop will falsely split any bare function name containing
.,_, or-. For example,FunctionName.Parse("get_weather", "_")produces PluginName="get", Name="weather", and the null check on line 185 passes because a non-null PluginName is always produced when the separator exists. Names with multiple underscores (e.g.,my_tools_v2_ReadFile) split incorrectly as plugin=my, function=tools_v2_ReadFile. The separator must come from the connector that formatted the name, not be guessed from the string.