From eb830a2f96269b23cb33f104652a9bae204d718b Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 15:47:40 +0200 Subject: [PATCH 1/6] Added IChatHistoryReducer for ChatCompletion with AzureOpenAI and OpenAI --- .../Agents/ChatCompletion_HistoryReducer.cs | 2 +- dotnet/src/Agents/Core/ChatHistoryChannel.cs | 4 +- .../src/Agents/Core/ChatHistoryKernelAgent.cs | 10 +- .../History/ChatHistoryReducerExtensions.cs | 2 +- .../ChatHistorySummarizationReducer.cs | 16 +- .../History/ChatHistoryTruncationReducer.cs | 8 +- .../Core/History/IAgentChatHistoryReducer.cs | 25 +++ .../Core/History/IChatHistoryReducer.cs | 32 ---- .../ChatHistoryReducerExtensionsTests.cs | 10 +- .../AzureOpenAIChatCompletionServiceTests.cs | 158 +++++++++++++++++ .../OpenAIChatCompletionServiceTests.cs | 159 ++++++++++++++++++ .../Core/ClientCore.ChatCompletion.cs | 11 +- .../ChatHistoryReducerExtensions.cs | 46 +++++ .../AI/ChatCompletion/IChatHistoryReducer.cs | 25 +++ 14 files changed, 446 insertions(+), 62 deletions(-) create mode 100644 dotnet/src/Agents/Core/History/IAgentChatHistoryReducer.cs delete mode 100644 dotnet/src/Agents/Core/History/IChatHistoryReducer.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_HistoryReducer.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_HistoryReducer.cs index 6e0816bc8470..9e9ccda9a224 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_HistoryReducer.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_HistoryReducer.cs @@ -80,7 +80,7 @@ private async Task InvokeAgentAsync(ChatCompletionAgent agent, int messageCount) Console.WriteLine($"# {AuthorRole.User}: '{index}'"); // Reduce prior to invoking the agent - bool isReduced = await agent.ReduceAsync(chat); + bool isReduced = await agent.TryReduceAsync(chat); // Invoke and display assistant response await foreach (ChatMessageContent message in agent.InvokeAsync(chat)) diff --git a/dotnet/src/Agents/Core/ChatHistoryChannel.cs b/dotnet/src/Agents/Core/ChatHistoryChannel.cs index 29eb89a447a7..a5fea6050d90 100644 --- a/dotnet/src/Agents/Core/ChatHistoryChannel.cs +++ b/dotnet/src/Agents/Core/ChatHistoryChannel.cs @@ -27,7 +27,7 @@ public sealed class ChatHistoryChannel : AgentChannel } // Pre-process history reduction. - await historyAgent.ReduceAsync(this._history, cancellationToken).ConfigureAwait(false); + await historyAgent.TryReduceAsync(this._history, cancellationToken).ConfigureAwait(false); // Capture the current message count to evaluate history mutation. int messageCount = this._history.Count; @@ -85,7 +85,7 @@ protected override async IAsyncEnumerable InvokeStr } // Pre-process history reduction. - await historyAgent.ReduceAsync(this._history, cancellationToken).ConfigureAwait(false); + await historyAgent.TryReduceAsync(this._history, cancellationToken).ConfigureAwait(false); int messageCount = this._history.Count; diff --git a/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs b/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs index aa28682173c7..7440e82e24fa 100644 --- a/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs +++ b/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs @@ -24,7 +24,7 @@ public abstract class ChatHistoryKernelAgent : KernelAgent public KernelArguments? Arguments { get; init; } /// - public IChatHistoryReducer? HistoryReducer { get; init; } + public IAgentChatHistoryReducer? HistoryReducer { get; init; } /// public abstract IAsyncEnumerable InvokeAsync( @@ -45,9 +45,9 @@ public abstract IAsyncEnumerable InvokeStreamingAsy /// /// The source history /// The to monitor for cancellation requests. The default is . - /// - public Task ReduceAsync(ChatHistory history, CancellationToken cancellationToken = default) => - history.ReduceAsync(this.HistoryReducer, cancellationToken); + /// A boolean indicating if the operation was successfull or not. + public Task TryReduceAsync(ChatHistory history, CancellationToken cancellationToken = default) => + history.TryReduceAsync(this.HistoryReducer, cancellationToken); /// protected sealed override IEnumerable GetChannelKeys() @@ -59,7 +59,7 @@ protected sealed override IEnumerable GetChannelKeys() if (this.HistoryReducer != null) { // Explicitly include the reducer type to eliminate the possibility of hash collisions - // with custom implementations of IChatHistoryReducer. + // with custom implementations of IAgentChatHistoryReducer. yield return this.HistoryReducer.GetType().FullName!; yield return this.HistoryReducer.GetHashCode().ToString(CultureInfo.InvariantCulture); diff --git a/dotnet/src/Agents/Core/History/ChatHistoryReducerExtensions.cs b/dotnet/src/Agents/Core/History/ChatHistoryReducerExtensions.cs index c884846baafa..3c5ff978f5d3 100644 --- a/dotnet/src/Agents/Core/History/ChatHistoryReducerExtensions.cs +++ b/dotnet/src/Agents/Core/History/ChatHistoryReducerExtensions.cs @@ -142,7 +142,7 @@ public static int LocateSafeReductionIndex(this IReadOnlyList for a reduction in collection size eliminates the need /// for re-allocation (of memory). /// - public static async Task ReduceAsync(this ChatHistory history, IChatHistoryReducer? reducer, CancellationToken cancellationToken) + public static async Task TryReduceAsync(this ChatHistory history, IAgentChatHistoryReducer? reducer, CancellationToken cancellationToken) { if (reducer == null) { diff --git a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs index 67720ab45112..a8efe855fdd3 100644 --- a/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs +++ b/dotnet/src/Agents/Core/History/ChatHistorySummarizationReducer.cs @@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Agents.History; /// is provided (recommended), reduction will scan within the threshold window in an attempt to /// avoid orphaning a user message from an assistant response. /// -public class ChatHistorySummarizationReducer : IChatHistoryReducer +public class ChatHistorySummarizationReducer : IAgentChatHistoryReducer { /// /// Metadata key to indicate a summary message. @@ -64,13 +64,13 @@ Provide a concise and complete summarization of the entire dialog that does not public bool UseSingleSummary { get; init; } = true; /// - public async Task?> ReduceAsync(IReadOnlyList history, CancellationToken cancellationToken = default) + public async Task?> ReduceAsync(IReadOnlyList chatHistory, CancellationToken cancellationToken = default) { // Identify where summary messages end and regular history begins - int insertionPoint = history.LocateSummarizationBoundary(SummaryMetadataKey); + int insertionPoint = chatHistory.LocateSummarizationBoundary(SummaryMetadataKey); // First pass to determine the truncation index - int truncationIndex = history.LocateSafeReductionIndex(this._targetCount, this._thresholdCount, insertionPoint); + int truncationIndex = chatHistory.LocateSafeReductionIndex(this._targetCount, this._thresholdCount, insertionPoint); IEnumerable? truncatedHistory = null; @@ -78,7 +78,7 @@ Provide a concise and complete summarization of the entire dialog that does not { // Second pass to extract history for summarization IEnumerable summarizedHistory = - history.Extract( + chatHistory.Extract( this.UseSingleSummary ? 0 : insertionPoint, truncationIndex - 1, (m) => m.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent)); @@ -111,7 +111,7 @@ IEnumerable AssemblySummarizedHistory(ChatMessageContent? su { for (int index = 0; index <= insertionPoint - 1; ++index) { - yield return history[index]; + yield return chatHistory[index]; } } @@ -120,9 +120,9 @@ IEnumerable AssemblySummarizedHistory(ChatMessageContent? su yield return summary; } - for (int index = truncationIndex; index < history.Count; ++index) + for (int index = truncationIndex; index < chatHistory.Count; ++index) { - yield return history[index]; + yield return chatHistory[index]; } } } diff --git a/dotnet/src/Agents/Core/History/ChatHistoryTruncationReducer.cs b/dotnet/src/Agents/Core/History/ChatHistoryTruncationReducer.cs index be9ca7868f87..c5723229b115 100644 --- a/dotnet/src/Agents/Core/History/ChatHistoryTruncationReducer.cs +++ b/dotnet/src/Agents/Core/History/ChatHistoryTruncationReducer.cs @@ -15,20 +15,20 @@ namespace Microsoft.SemanticKernel.Agents.History; /// is provided (recommended), reduction will scan within the threshold window in an attempt to /// avoid orphaning a user message from an assistant response. /// -public class ChatHistoryTruncationReducer : IChatHistoryReducer +public class ChatHistoryTruncationReducer : IAgentChatHistoryReducer { /// - public Task?> ReduceAsync(IReadOnlyList history, CancellationToken cancellationToken = default) + public Task?> ReduceAsync(IReadOnlyList chatHistory, CancellationToken cancellationToken = default) { // First pass to determine the truncation index - int truncationIndex = history.LocateSafeReductionIndex(this._targetCount, this._thresholdCount); + int truncationIndex = chatHistory.LocateSafeReductionIndex(this._targetCount, this._thresholdCount); IEnumerable? truncatedHistory = null; if (truncationIndex > 0) { // Second pass to truncate the history - truncatedHistory = history.Extract(truncationIndex); + truncatedHistory = chatHistory.Extract(truncationIndex); } return Task.FromResult(truncatedHistory); diff --git a/dotnet/src/Agents/Core/History/IAgentChatHistoryReducer.cs b/dotnet/src/Agents/Core/History/IAgentChatHistoryReducer.cs new file mode 100644 index 000000000000..1cb980eb5e54 --- /dev/null +++ b/dotnet/src/Agents/Core/History/IAgentChatHistoryReducer.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Microsoft.SemanticKernel.Agents.History; + +/// +/// Defines a contract for a reducing chat history to be used by agents. +/// +/// +/// The additional interface methods are used to evaluate the equality of different reducers, which is necessary for the agent channel key. +/// +public interface IAgentChatHistoryReducer : IChatHistoryReducer +{ + /// + /// Each reducer shall override equality evaluation so that different reducers + /// of the same configuration can be evaluated for equivalency. + /// + bool Equals(object? obj); + + /// + /// Each reducer shall implement custom hash-code generation so that different reducers + /// of the same configuration can be evaluated for equivalency. + /// + int GetHashCode(); +} diff --git a/dotnet/src/Agents/Core/History/IChatHistoryReducer.cs b/dotnet/src/Agents/Core/History/IChatHistoryReducer.cs deleted file mode 100644 index 884fbcf42bc1..000000000000 --- a/dotnet/src/Agents/Core/History/IChatHistoryReducer.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.SemanticKernel.Agents.History; - -/// -/// Defines a contract for a reducing chat history. -/// -public interface IChatHistoryReducer -{ - /// - /// Each reducer shall override equality evaluation so that different reducers - /// of the same configuration can be evaluated for equivalency. - /// - bool Equals(object? obj); - - /// - /// Each reducer shall implement custom hash-code generation so that different reducers - /// of the same configuration can be evaluated for equivalency. - /// - int GetHashCode(); - - /// - /// Optionally reduces the chat history. - /// - /// The source history (which may have been previously reduced) - /// The to monitor for cancellation requests. The default is . - /// The reduced history, or 'null' if no reduction has occurred - Task?> ReduceAsync(IReadOnlyList history, CancellationToken cancellationToken = default); -} diff --git a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs index d9042305d9fa..aa2a1ecba7b7 100644 --- a/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/History/ChatHistoryReducerExtensionsTests.cs @@ -85,18 +85,18 @@ public async Task VerifyChatHistoryNotReducedAsync() { // Arrange ChatHistory history = []; - Mock mockReducer = new(); + Mock mockReducer = new(); mockReducer.Setup(r => r.ReduceAsync(It.IsAny>(), default)).ReturnsAsync((IEnumerable?)null); // Act - bool isReduced = await history.ReduceAsync(null, default); + bool isReduced = await history.TryReduceAsync(null, default); // Assert Assert.False(isReduced); Assert.Empty(history); // Act - isReduced = await history.ReduceAsync(mockReducer.Object, default); + isReduced = await history.TryReduceAsync(mockReducer.Object, default); // Assert Assert.False(isReduced); @@ -110,13 +110,13 @@ public async Task VerifyChatHistoryNotReducedAsync() public async Task VerifyChatHistoryReducedAsync() { // Arrange - Mock mockReducer = new(); + Mock mockReducer = new(); mockReducer.Setup(r => r.ReduceAsync(It.IsAny>(), default)).ReturnsAsync((IEnumerable?)[]); ChatHistory history = [.. MockHistoryGenerator.CreateSimpleHistory(10)]; // Act - bool isReduced = await history.ReduceAsync(mockReducer.Object, default); + bool isReduced = await history.TryReduceAsync(mockReducer.Object, default); // Assert Assert.True(isReduced); diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs index 23a15e33a359..bd284b9b23fc 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs @@ -8,6 +8,7 @@ using System.Net.Http; using System.Text; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Azure.AI.OpenAI; using Azure.AI.OpenAI.Chat; @@ -15,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; @@ -1223,6 +1225,162 @@ static void MutateChatHistory(AutoFunctionInvocationContext context, Func(); + moqReducer.Setup(x => x.ReduceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync((ChatHistory chatHistory, CancellationToken ct) => + { + var duplicatedChatHistory = chatHistory.ToList(); + duplicatedChatHistory.RemoveRange(1, 2); + + return duplicatedChatHistory; + }); + + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.Services.AddSingleton(moqReducer.Object); + var kernel = kernelBuilder.Build(); + kernel.ImportPluginFromFunctions("MyPlugin", [KernelFunctionFactory.CreateFromMethod(() => "rainy", "GetCurrentWeather")]); + + using var firstResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_single_function_call_test_response.json")) }; + this._messageHandlerStub.ResponsesToReturn.Add(firstResponse); + + using var secondResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_test_response.json")) }; + this._messageHandlerStub.ResponsesToReturn.Add(secondResponse); + + var sut = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient); + + var chatHistory = new ChatHistory + { + new ChatMessageContent(AuthorRole.User, "What time is it?"), + new ChatMessageContent(AuthorRole.Assistant, [ + new FunctionCallContent("Date", "TimePlugin", "2") + ]), + new ChatMessageContent(AuthorRole.Tool, [ + new FunctionResultContent("Date", "TimePlugin", "2", "rainy") + ]), + new ChatMessageContent(AuthorRole.Assistant, "08/06/2024 00:00:00"), + new ChatMessageContent(AuthorRole.User, "Given the current time of day and weather, what is the likely color of the sky in Boston?") + }; + + // Act + await sut.GetChatMessageContentAsync(chatHistory, new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, kernel); + + // Assert + var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContents[1]!); + Assert.NotNull(actualRequestContent); + + var optionsJson = JsonSerializer.Deserialize(actualRequestContent); + + var messages = optionsJson.GetProperty("messages"); + Assert.Equal(5, messages.GetArrayLength()); + + var userFirstPrompt = messages[0]; + Assert.Equal("user", userFirstPrompt.GetProperty("role").GetString()); + Assert.Equal("What time is it?", userFirstPrompt.GetProperty("content").ToString()); + + var assistantFirstResponse = messages[1]; + Assert.Equal("assistant", assistantFirstResponse.GetProperty("role").GetString()); + Assert.Equal("08/06/2024 00:00:00", assistantFirstResponse.GetProperty("content").GetString()); + + var userSecondPrompt = messages[2]; + Assert.Equal("user", userSecondPrompt.GetProperty("role").GetString()); + Assert.Equal("Given the current time of day and weather, what is the likely color of the sky in Boston?", userSecondPrompt.GetProperty("content").ToString()); + + var assistantSecondResponse = messages[3]; + Assert.Equal("assistant", assistantSecondResponse.GetProperty("role").GetString()); + Assert.Equal("1", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("id").GetString()); + Assert.Equal("MyPlugin-GetCurrentWeather", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("function").GetProperty("name").GetString()); + + var functionResult = messages[4]; + Assert.Equal("tool", functionResult.GetProperty("role").GetString()); + Assert.Equal("rainy", functionResult.GetProperty("content").GetString()); + + // Verify that Chat History instance was not mutated and contains all messages + Assert.Equal(7, chatHistory.Count); + } + + [Fact] + public async Task GetStreamingChatMessageContentsShouldSendReducedChatHistoryToLLM() + { + // Arrange + var moqReducer = new Mock(); + moqReducer.Setup(x => x.ReduceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync((ChatHistory chatHistory, CancellationToken ct) => + { + var duplicatedChatHistory = chatHistory.ToList(); + duplicatedChatHistory.RemoveRange(1, 2); + + return duplicatedChatHistory; + }); + + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.Services.AddSingleton(moqReducer.Object); + var kernel = kernelBuilder.Build(); + kernel.ImportPluginFromFunctions("MyPlugin", [KernelFunctionFactory.CreateFromMethod(() => "rainy", "GetCurrentWeather")]); + + using var firstResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_streaming_single_function_call_test_response.txt")) }; + this._messageHandlerStub.ResponsesToReturn.Add(firstResponse); + + using var secondResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_streaming_test_response.txt")) }; + this._messageHandlerStub.ResponsesToReturn.Add(secondResponse); + + var sut = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient); + + var chatHistory = new ChatHistory + { + new ChatMessageContent(AuthorRole.User, "What time is it?"), + new ChatMessageContent(AuthorRole.Assistant, [ + new FunctionCallContent("Date", "TimePlugin", "2") + ]), + new ChatMessageContent(AuthorRole.Tool, [ + new FunctionResultContent("Date", "TimePlugin", "2", "rainy") + ]), + new ChatMessageContent(AuthorRole.Assistant, "08/06/2024 00:00:00"), + new ChatMessageContent(AuthorRole.User, "Given the current time of day and weather, what is the likely color of the sky in Boston?") + }; + + // Act + await foreach (var update in sut.GetStreamingChatMessageContentsAsync(chatHistory, new AzureOpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, kernel)) + { + } + + // Assert + var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContents[1]!); + Assert.NotNull(actualRequestContent); + + var optionsJson = JsonSerializer.Deserialize(actualRequestContent); + + var messages = optionsJson.GetProperty("messages"); + Assert.Equal(5, messages.GetArrayLength()); + + var userFirstPrompt = messages[0]; + Assert.Equal("user", userFirstPrompt.GetProperty("role").GetString()); + Assert.Equal("What time is it?", userFirstPrompt.GetProperty("content").ToString()); + + var assistantFirstResponse = messages[1]; + Assert.Equal("assistant", assistantFirstResponse.GetProperty("role").GetString()); + Assert.Equal("08/06/2024 00:00:00", assistantFirstResponse.GetProperty("content").GetString()); + + var userSecondPrompt = messages[2]; + Assert.Equal("user", userSecondPrompt.GetProperty("role").GetString()); + Assert.Equal("Given the current time of day and weather, what is the likely color of the sky in Boston?", userSecondPrompt.GetProperty("content").ToString()); + + var assistantSecondResponse = messages[3]; + Assert.Equal("assistant", assistantSecondResponse.GetProperty("role").GetString()); + Assert.Equal("1", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("id").GetString()); + Assert.Equal("MyPlugin-GetCurrentWeather", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("function").GetProperty("name").GetString()); + + var functionResult = messages[4]; + Assert.Equal("tool", functionResult.GetProperty("role").GetString()); + Assert.Equal("rainy", functionResult.GetProperty("content").GetString()); + + // Verify that Chat History instance was not mutated and contains all messages + Assert.Equal(7, chatHistory.Count); + } + [Fact] public async Task ItCreatesCorrectFunctionToolCallsWhenUsingAutoFunctionChoiceBehaviorAsync() { diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs index 658709cf5b14..41c43fcadf9c 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs @@ -10,10 +10,12 @@ using System.Net.Http; using System.Text; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Diagnostics; @@ -1032,6 +1034,163 @@ static void MutateChatHistory(AutoFunctionInvocationContext context, Func(); + moqReducer.Setup(x => x.ReduceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync((ChatHistory chatHistory, CancellationToken ct) => + { + var duplicatedChatHistory = chatHistory.ToList(); + duplicatedChatHistory.RemoveRange(1, 2); + + return duplicatedChatHistory; + }); + + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.Services.AddSingleton(moqReducer.Object); + var kernel = kernelBuilder.Build(); + kernel.ImportPluginFromFunctions("MyPlugin", [KernelFunctionFactory.CreateFromMethod(() => "rainy", "GetCurrentWeather")]); + + using var firstResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_single_function_call_test_response.json")) }; + this._messageHandlerStub.ResponseQueue.Enqueue(firstResponse); + + using var secondResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_test_response.json")) }; + this._messageHandlerStub.ResponseQueue.Enqueue(secondResponse); + + var sut = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient); + + var chatHistory = new ChatHistory + { + new ChatMessageContent(AuthorRole.User, "What time is it?"), + new ChatMessageContent(AuthorRole.Assistant, [ + new FunctionCallContent("Date", "TimePlugin", "2") + ]), + new ChatMessageContent(AuthorRole.Tool, [ + new FunctionResultContent("Date", "TimePlugin", "2", "rainy") + ]), + new ChatMessageContent(AuthorRole.Assistant, "08/06/2024 00:00:00"), + new ChatMessageContent(AuthorRole.User, "Given the current time of day and weather, what is the likely color of the sky in Boston?") + }; + + // Act + await sut.GetChatMessageContentAsync(chatHistory, new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, kernel); + + // Assert + var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!); + Assert.NotNull(actualRequestContent); + + var optionsJson = JsonSerializer.Deserialize(actualRequestContent); + + var messages = optionsJson.GetProperty("messages"); + Assert.Equal(5, messages.GetArrayLength()); + + var userFirstPrompt = messages[0]; + Assert.Equal("user", userFirstPrompt.GetProperty("role").GetString()); + Assert.Equal("What time is it?", userFirstPrompt.GetProperty("content").ToString()); + + var assistantFirstResponse = messages[1]; + Assert.Equal("assistant", assistantFirstResponse.GetProperty("role").GetString()); + Assert.Equal("08/06/2024 00:00:00", assistantFirstResponse.GetProperty("content").GetString()); + + var userSecondPrompt = messages[2]; + Assert.Equal("user", userSecondPrompt.GetProperty("role").GetString()); + Assert.Equal("Given the current time of day and weather, what is the likely color of the sky in Boston?", userSecondPrompt.GetProperty("content").ToString()); + + var assistantSecondResponse = messages[3]; + Assert.Equal("assistant", assistantSecondResponse.GetProperty("role").GetString()); + Assert.Equal("1", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("id").GetString()); + Assert.Equal("MyPlugin-GetCurrentWeather", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("function").GetProperty("name").GetString()); + + var functionResult = messages[4]; + Assert.Equal("tool", functionResult.GetProperty("role").GetString()); + Assert.Equal("rainy", functionResult.GetProperty("content").GetString()); + + // Verify that Chat History instance was not mutated and contains all messages + Assert.Equal(7, chatHistory.Count); + } + + [Fact] + public async Task GetStreamingChatMessageContentsShouldSendReducedChatHistoryToLLM() + { + // Arrange + var moqReducer = new Mock(); + moqReducer.Setup(x => x.ReduceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync((ChatHistory chatHistory, CancellationToken ct) => + { + var duplicatedChatHistory = chatHistory.ToList(); + duplicatedChatHistory.RemoveRange(1, 2); + + return duplicatedChatHistory; + }); + + var kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.Services.AddSingleton(moqReducer.Object); + var kernel = kernelBuilder.Build(); + kernel.ImportPluginFromFunctions("MyPlugin", [KernelFunctionFactory.CreateFromMethod(() => "rainy", "GetCurrentWeather")]); + + using var firstResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_streaming_single_function_call_test_response.txt")) }; + this._messageHandlerStub.ResponseQueue.Enqueue(firstResponse); + + using var secondResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(File.OpenRead("TestData/chat_completion_streaming_test_response.txt")) }; + this._messageHandlerStub.ResponseQueue.Enqueue(secondResponse); + + var sut = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient); + + var chatHistory = new ChatHistory + { + new ChatMessageContent(AuthorRole.User, "What time is it?"), + new ChatMessageContent(AuthorRole.Assistant, [ + new FunctionCallContent("Date", "TimePlugin", "2") + ]), + new ChatMessageContent(AuthorRole.Tool, [ + new FunctionResultContent("Date", "TimePlugin", "2", "rainy") + ]), + new ChatMessageContent(AuthorRole.Assistant, "08/06/2024 00:00:00"), + new ChatMessageContent(AuthorRole.User, "Given the current time of day and weather, what is the likely color of the sky in Boston?") + }; + + // Act + await foreach (var update in sut.GetStreamingChatMessageContentsAsync(chatHistory, new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }, kernel)) + { + } + + // Assert + var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!); + Assert.NotNull(actualRequestContent); + + var optionsJson = JsonSerializer.Deserialize(actualRequestContent); + + var messages = optionsJson.GetProperty("messages"); + Assert.Equal(5, messages.GetArrayLength()); + + var userFirstPrompt = messages[0]; + Assert.Equal("user", userFirstPrompt.GetProperty("role").GetString()); + Assert.Equal("What time is it?", userFirstPrompt.GetProperty("content").ToString()); + + var assistantFirstResponse = messages[1]; + Assert.Equal("assistant", assistantFirstResponse.GetProperty("role").GetString()); + Assert.Equal("08/06/2024 00:00:00", assistantFirstResponse.GetProperty("content").GetString()); + + var userSecondPrompt = messages[2]; + Assert.Equal("user", userSecondPrompt.GetProperty("role").GetString()); + Assert.Equal("Given the current time of day and weather, what is the likely color of the sky in Boston?", userSecondPrompt.GetProperty("content").ToString()); + + var assistantSecondResponse = messages[3]; + Assert.Equal("assistant", assistantSecondResponse.GetProperty("role").GetString()); + Assert.Equal("1", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("id").GetString()); + Assert.Equal("MyPlugin-GetCurrentWeather", assistantSecondResponse.GetProperty("tool_calls")[0].GetProperty("function").GetProperty("name").GetString()); + + var functionResult = messages[4]; + Assert.Equal("tool", functionResult.GetProperty("role").GetString()); + Assert.Equal("rainy", functionResult.GetProperty("content").GetString()); + + // Verify that Chat History instance was not mutated and contains all messages + Assert.Equal(7, chatHistory.Count); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs index 9f1120d7c651..7ee8c169f8ee 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using JsonSchemaMapper; using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Diagnostics; using OpenAI.Chat; @@ -156,7 +157,7 @@ internal async Task> GetChatMessageContentsAsy for (int requestIndex = 0; ; requestIndex++) { - var chatForRequest = CreateChatCompletionMessages(chatExecutionSettings, chatHistory); + var chatForRequest = await CreateChatCompletionMessagesAsync(chatExecutionSettings, chatHistory, kernel, cancellationToken).ConfigureAwait(false); var functionCallingConfig = this.GetFunctionCallingConfiguration(kernel, chatExecutionSettings, chatHistory, requestIndex); @@ -244,7 +245,7 @@ internal async IAsyncEnumerable GetStreamingC for (int requestIndex = 0; ; requestIndex++) { - var chatForRequest = CreateChatCompletionMessages(chatExecutionSettings, chatHistory); + var chatForRequest = await CreateChatCompletionMessagesAsync(chatExecutionSettings, chatHistory, kernel, cancellationToken).ConfigureAwait(false); var toolCallingConfig = this.GetFunctionCallingConfiguration(kernel, chatExecutionSettings, chatHistory, requestIndex); @@ -606,8 +607,10 @@ private static ChatHistory CreateNewChat(string? text = null, OpenAIPromptExecut return chat; } - private static List CreateChatCompletionMessages(OpenAIPromptExecutionSettings executionSettings, ChatHistory chatHistory) + private static async Task> CreateChatCompletionMessagesAsync(OpenAIPromptExecutionSettings executionSettings, ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) { + var sourceMessages = await chatHistory.TryReduceAsync(kernel, cancellationToken).ConfigureAwait(false); + List messages = []; if (!string.IsNullOrWhiteSpace(executionSettings.ChatSystemPrompt) && !chatHistory.Any(m => m.Role == AuthorRole.System)) @@ -615,7 +618,7 @@ private static List CreateChatCompletionMessages(OpenAIPromptExecut messages.Add(new SystemChatMessage(executionSettings.ChatSystemPrompt)); } - foreach (var message in chatHistory) + foreach (var message in sourceMessages) { messages.AddRange(CreateRequestMessages(message)); } diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs new file mode 100644 index 000000000000..6ed8cad75829 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Microsoft.SemanticKernel.AI.ChatCompletion; + +/// +/// Class sponsor that holds extension methods for which applied the . +/// +public static class ChatHistoryReducerExtensions +{ + /// + /// Try to reduce the chat history before sending it to the chat completion provider. + /// + /// + /// If there is no registered in the , the original chat history will be returned. + /// If the returns null, the original chat history will be returned. + /// Note: This is not mutating the original chat history. + /// + /// The current chat history, including system messages, user messages, assistant messages and tool invocations. + /// The containing services, plugins, and other state for use throughout the operation. + /// The to cancell this operation /> + /// + public static async Task> TryReduceAsync(this ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) + { + if (kernel is null) + { + return chatHistory; + } + + var reducer = kernel.Services.GetService(); + + if (reducer is null) + { + return chatHistory; + } + + var reduced = await reducer.ReduceAsync(chatHistory, cancellationToken).ConfigureAwait(false); + + return reduced ?? chatHistory; + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs new file mode 100644 index 000000000000..26c179bcd365 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.ChatCompletion; + +/// +/// Interface for reducing the chat history before sending it to the chat completion provider. +/// +/// +/// This interface can be implemented in order to apply custom logic for prompt truncation during inference. +/// +public interface IChatHistoryReducer +{ + /// + /// Reduces the chat history to a smaller size before sending it to the chat completion provider. + /// + /// The current chat history, including system messages, user messages, assistant messages and tool invocations. + /// The to monitor for cancellation requests. + /// + /// The reduced history, or 'null' if no reduction has occurred + Task?> ReduceAsync(IReadOnlyList chatHistory, CancellationToken cancellationToken); +} From 228cbb8a6285d5a624d6cafad87515dc5743bf9a Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 16:00:25 +0200 Subject: [PATCH 2/6] Fixed typos and comments --- dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs | 2 +- .../Services/OpenAIChatCompletionServiceTests.cs | 1 - .../AI/ChatCompletion/ChatHistoryReducerExtensions.cs | 2 +- .../AI/ChatCompletion/IChatHistoryReducer.cs | 3 +-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs b/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs index 7440e82e24fa..bce756ebff0c 100644 --- a/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs +++ b/dotnet/src/Agents/Core/ChatHistoryKernelAgent.cs @@ -45,7 +45,7 @@ public abstract IAsyncEnumerable InvokeStreamingAsy /// /// The source history /// The to monitor for cancellation requests. The default is . - /// A boolean indicating if the operation was successfull or not. + /// A boolean indicating if the operation was successful or not. public Task TryReduceAsync(ChatHistory history, CancellationToken cancellationToken = default) => history.TryReduceAsync(this.HistoryReducer, cancellationToken); diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs index 41c43fcadf9c..f42dfa3f9ceb 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs @@ -1038,7 +1038,6 @@ static void MutateChatHistory(AutoFunctionInvocationContext context, Func(); moqReducer.Setup(x => x.ReduceAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((ChatHistory chatHistory, CancellationToken ct) => diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs index 6ed8cad75829..d8646dda9899 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs @@ -23,7 +23,7 @@ public static class ChatHistoryReducerExtensions /// /// The current chat history, including system messages, user messages, assistant messages and tool invocations. /// The containing services, plugins, and other state for use throughout the operation. - /// The to cancell this operation /> + /// The to cancel this operation. /// public static async Task> TryReduceAsync(this ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) { diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs index 26c179bcd365..da5797b1e740 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatHistoryReducer.cs @@ -19,7 +19,6 @@ public interface IChatHistoryReducer /// /// The current chat history, including system messages, user messages, assistant messages and tool invocations. /// The to monitor for cancellation requests. - /// - /// The reduced history, or 'null' if no reduction has occurred + /// The reduced history, or 'null' if no reduction has occurred. Task?> ReduceAsync(IReadOnlyList chatHistory, CancellationToken cancellationToken); } From fac6798a6a2d635861fb3ff8f3675ba161a3275c Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 16:32:49 +0200 Subject: [PATCH 3/6] Removed unnecessary `using` --- .../Services/AzureOpenAIChatCompletionServiceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs index bd284b9b23fc..f20dbf48d9d4 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.Connectors.OpenAI; From a812d12ee74712775c55604666927a8e7f9fce57 Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 16:58:23 +0200 Subject: [PATCH 4/6] Added comment to retrigger CI. Had issue with npkg download. --- .../AI/ChatCompletion/ChatHistoryReducerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs index d8646dda9899..e3583287a1ce 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs @@ -24,7 +24,7 @@ public static class ChatHistoryReducerExtensions /// The current chat history, including system messages, user messages, assistant messages and tool invocations. /// The containing services, plugins, and other state for use throughout the operation. /// The to cancel this operation. - /// + /// The reduced chat history if the kernel had one reducer configured, or the same if it cannot be reduced. public static async Task> TryReduceAsync(this ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) { if (kernel is null) From 53ab30bbe37714c55cf036663d50c601f7098046 Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 17:21:22 +0200 Subject: [PATCH 5/6] Removed unused `using` --- .../Services/OpenAIChatCompletionServiceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs index f42dfa3f9ceb..900f7ebd731f 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs @@ -15,7 +15,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Diagnostics; From 14db8012ba65b668691a62ac5902a5ff4b711f27 Mon Sep 17 00:00:00 2001 From: Sandro Hanea Date: Wed, 18 Sep 2024 17:42:35 +0200 Subject: [PATCH 6/6] Renamed TryReduceAsync to ReduceAsync --- .../Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs | 2 +- .../AI/ChatCompletion/ChatHistoryReducerExtensions.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs index 7ee8c169f8ee..5e6abe2019bd 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs @@ -609,7 +609,7 @@ private static ChatHistory CreateNewChat(string? text = null, OpenAIPromptExecut private static async Task> CreateChatCompletionMessagesAsync(OpenAIPromptExecutionSettings executionSettings, ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) { - var sourceMessages = await chatHistory.TryReduceAsync(kernel, cancellationToken).ConfigureAwait(false); + var sourceMessages = await chatHistory.ReduceAsync(kernel, cancellationToken).ConfigureAwait(false); List messages = []; diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs index e3583287a1ce..186d60a77655 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatHistoryReducerExtensions.cs @@ -14,7 +14,7 @@ namespace Microsoft.SemanticKernel.AI.ChatCompletion; public static class ChatHistoryReducerExtensions { /// - /// Try to reduce the chat history before sending it to the chat completion provider. + /// Reduces the chat history before sending it to the chat completion provider. /// /// /// If there is no registered in the , the original chat history will be returned. @@ -25,7 +25,7 @@ public static class ChatHistoryReducerExtensions /// The containing services, plugins, and other state for use throughout the operation. /// The to cancel this operation. /// The reduced chat history if the kernel had one reducer configured, or the same if it cannot be reduced. - public static async Task> TryReduceAsync(this ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) + public static async Task> ReduceAsync(this ChatHistory chatHistory, Kernel? kernel, CancellationToken cancellationToken) { if (kernel is null) {