Skip to content
Open
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 @@ -35,14 +35,8 @@ internal static ChatMessageContent ToChatMessageContent(this ChatMessage message
Microsoft.Extensions.AI.UriContent uc when uc.HasTopLevelMediaType("audio") => new Microsoft.SemanticKernel.AudioContent(uc.Uri),
Microsoft.Extensions.AI.DataContent dc => new Microsoft.SemanticKernel.BinaryContent(dc.Uri),
Microsoft.Extensions.AI.UriContent uc => new Microsoft.SemanticKernel.BinaryContent(uc.Uri),
Microsoft.Extensions.AI.FunctionCallContent fcc => new Microsoft.SemanticKernel.FunctionCallContent(
functionName: fcc.Name,
id: fcc.CallId,
arguments: fcc.Arguments is not null ? new(fcc.Arguments) : null),
Microsoft.Extensions.AI.FunctionResultContent frc => new Microsoft.SemanticKernel.FunctionResultContent(
functionName: GetFunctionCallContent(frc.CallId)?.Name,
callId: frc.CallId,
result: frc.Result),
Microsoft.Extensions.AI.FunctionCallContent fcc => CreateFunctionCallContent(fcc),
Microsoft.Extensions.AI.FunctionResultContent frc => CreateFunctionResultContent(frc),
_ => null
};
#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Expand All @@ -58,6 +52,71 @@ internal static ChatMessageContent ToChatMessageContent(this ChatMessage message

return result;

Microsoft.SemanticKernel.FunctionCallContent CreateFunctionCallContent(Microsoft.Extensions.AI.FunctionCallContent functionCallContent)
{
var (functionName, pluginName) = ParseFunctionName(functionCallContent.Name);

return new Microsoft.SemanticKernel.FunctionCallContent(
functionName: functionName,
pluginName: pluginName,
id: functionCallContent.CallId,
arguments: functionCallContent.Arguments is not null ? new(functionCallContent.Arguments) : null);
}

Microsoft.SemanticKernel.FunctionResultContent CreateFunctionResultContent(Microsoft.Extensions.AI.FunctionResultContent functionResultContent)
{
string? functionName = null;
string? pluginName = null;

if (GetFunctionCallContent(functionResultContent.CallId) is { } functionCallContent)
{
(functionName, pluginName) = ParseFunctionName(functionCallContent.Name);
}

return new Microsoft.SemanticKernel.FunctionResultContent(
functionName: functionName,
pluginName: pluginName,
callId: functionResultContent.CallId,
result: functionResultContent.Result);
}

static (string FunctionName, string? PluginName) ParseFunctionName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
{
return (string.Empty, null);
}

// Most connectors use "." or "-" as the fully-qualified separator.
var parsed = FunctionName.Parse(name, ".");
if (!string.IsNullOrEmpty(parsed.PluginName))
{
return (parsed.Name, parsed.PluginName);
}

parsed = FunctionName.Parse(name, "-");
if (!string.IsNullOrEmpty(parsed.PluginName))
{
return (parsed.Name, parsed.PluginName);
}

// Some Ollama models return tool names as "<plugin>_<FunctionName>".
int underscore = name.IndexOf('_');
if (underscore > 0 &&
underscore == name.LastIndexOf('_') &&
underscore + 1 < name.Length &&
char.IsUpper(name[underscore + 1]))
{
parsed = FunctionName.Parse(name, "_");
if (!string.IsNullOrEmpty(parsed.PluginName))
{
return (parsed.Name, parsed.PluginName);
}
}

return (name, null);
}

Microsoft.Extensions.AI.FunctionCallContent? GetFunctionCallContent(string callId)
=> response?.Messages
.Select(m => m.Contents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,44 @@ public void ToChatMessageContentWithFunctionCallContentCreatesFunctionCallConten
Assert.NotNull(functionCall.Arguments);
}

[Fact]
public void ToChatMessageContentWithUnderscoreQualifiedFunctionCallParsesPluginAndFunction()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.Assistant, [
new Microsoft.Extensions.AI.FunctionCallContent("call-123", "time_ReadFile")
]);

// Act
var result = chatMessage.ToChatMessageContent();

// Assert
Assert.NotNull(result);
Assert.Single(result.Items);
var functionCall = Assert.IsType<Microsoft.SemanticKernel.FunctionCallContent>(result.Items[0]);
Assert.Equal("time", functionCall.PluginName);
Assert.Equal("ReadFile", functionCall.FunctionName);
}

[Fact]
public void ToChatMessageContentWithSnakeCaseFunctionNameDoesNotSplitIntoPluginAndFunction()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.Assistant, [
new Microsoft.Extensions.AI.FunctionCallContent("call-123", "my_function")
]);

// Act
var result = chatMessage.ToChatMessageContent();

// Assert
Assert.NotNull(result);
Assert.Single(result.Items);
var functionCall = Assert.IsType<Microsoft.SemanticKernel.FunctionCallContent>(result.Items[0]);
Assert.Null(functionCall.PluginName);
Assert.Equal("my_function", functionCall.FunctionName);
}

[Fact]
public void ToChatMessageContentWithFunctionResultContentCreatesFunctionResultContent()
{
Expand All @@ -224,6 +262,30 @@ public void ToChatMessageContentWithFunctionResultContentCreatesFunctionResultCo
Assert.Equal("result value", functionResult.Result);
}

[Fact]
public void ToChatMessageContentWithFunctionResultContentParsesPluginNameFromMatchedFunctionCall()
{
// Arrange
var functionCallMessage = new ChatMessage(ChatRole.Assistant, [
new Microsoft.Extensions.AI.FunctionCallContent("call-123", "time_ReadFile")
]);
var resultMessage = new ChatMessage(ChatRole.Tool, [
new Microsoft.Extensions.AI.FunctionResultContent("call-123", "result value")
]);
var response = new ChatResponse(new[] { functionCallMessage, resultMessage });

// Act
var result = resultMessage.ToChatMessageContent(response);

// Assert
Assert.NotNull(result);
Assert.Single(result.Items);
var functionResult = Assert.IsType<Microsoft.SemanticKernel.FunctionResultContent>(result.Items[0]);
Assert.Equal("time", functionResult.PluginName);
Assert.Equal("ReadFile", functionResult.FunctionName);
Assert.Equal("result value", functionResult.Result);
}

[Fact]
public void ToChatMessageContentWithMultipleContentItemsCreatesMultipleItems()
{
Expand Down