diff --git a/dotnet/src/InternalUtilities/connectors/AI/FunctionCalling/FunctionCallsProcessor.cs b/dotnet/src/InternalUtilities/connectors/AI/FunctionCalling/FunctionCallsProcessor.cs index ddca98c7c053..f54d0e3784bb 100644 --- a/dotnet/src/InternalUtilities/connectors/AI/FunctionCalling/FunctionCallsProcessor.cs +++ b/dotnet/src/InternalUtilities/connectors/AI/FunctionCalling/FunctionCallsProcessor.cs @@ -389,6 +389,7 @@ await this.OnAutoFunctionInvocationAsync( private void AddFunctionCallResultToChatHistory(ChatHistory chatHistory, FunctionResultContext resultContext) { var message = new ChatMessageContent(role: AuthorRole.Tool, content: resultContext.Result); + message.Metadata = resultContext.Context.Result.Metadata; message.Items.Add(this.GenerateResultContent(resultContext)); chatHistory.Add(message); } diff --git a/dotnet/src/SemanticKernel.UnitTests/Utilities/AIConnectors/FunctionCallsProcessorTests.cs b/dotnet/src/SemanticKernel.UnitTests/Utilities/AIConnectors/FunctionCallsProcessorTests.cs index e1258d124c6a..6119ca7d9684 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Utilities/AIConnectors/FunctionCallsProcessorTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Utilities/AIConnectors/FunctionCallsProcessorTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading; @@ -855,6 +856,54 @@ public void ItShouldSerializeFunctionResultsWithStringProperties() Assert.Equal("{\"Text\":\"テスト\"}", result); } + [Fact] + public async Task ItShouldPropagateFunctionResultMetadataToChatHistoryAsync() + { + // Arrange + var expectedMetadata = new Dictionary + { + ["key1"] = "value1", + ["key2"] = 42 + }; + + var function1 = KernelFunctionFactory.CreateFromMethod((string parameter) => parameter, "Function1"); + var plugin = KernelPluginFactory.CreateFromFunctions("MyPlugin", [function1]); + + var kernel = CreateKernel(plugin, async (context, next) => + { + await next(context); + + // Filter sets metadata on the FunctionResult + context.Result = new FunctionResult(context.Function, context.Result.GetValue(), context.Result.Culture, expectedMetadata); + }); + + var chatMessageContent = new ChatMessageContent(); + chatMessageContent.Items.Add(new FunctionCallContent("Function1", "MyPlugin", arguments: new KernelArguments() { ["parameter"] = "function1-result" })); + + var chatHistory = new ChatHistory(); + + // Act + await this._sut.ProcessFunctionCallsAsync( + chatMessageContent: chatMessageContent, + executionSettings: this._promptExecutionSettings, + chatHistory: chatHistory, + requestIndex: 0, + checkIfFunctionAdvertised: (_) => true, + options: this._functionChoiceBehaviorOptions, + kernel: kernel!, + isStreaming: false, + cancellationToken: CancellationToken.None); + + // Assert + Assert.Equal(2, chatHistory.Count); + + var toolMessage = chatHistory[1]; // First is the assistant message, second is the tool result + Assert.Equal(AuthorRole.Tool, toolMessage.Role); + Assert.NotNull(toolMessage.Metadata); + Assert.Equal("value1", toolMessage.Metadata["key1"]); + Assert.Equal(42, toolMessage.Metadata["key2"]); + } + [Fact] public async Task ItShouldPassPromptExecutionSettingsToAutoFunctionInvocationFilterAsync() {