// Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; namespace ChatCompletion; /// /// Implementation of which trim to the specified max token count. /// /// /// This reducer requires that the ChatMessageContent.MetaData contains a TokenCount property. /// public sealed class ChatHistoryMaxTokensReducer : IChatHistoryReducer { private readonly int _maxTokenCount; /// /// Creates a new instance of . /// /// Max token count to send to the model. public ChatHistoryMaxTokensReducer(int maxTokenCount) { if (maxTokenCount <= 0) { throw new ArgumentException("Maximum token count must be greater than zero.", nameof(maxTokenCount)); } this._maxTokenCount = maxTokenCount; } /// public Task?> ReduceAsync(IReadOnlyList chatHistory, CancellationToken cancellationToken = default) { var systemMessage = chatHistory.GetSystemMessage(); var truncationIndex = ComputeTruncationIndex(chatHistory, systemMessage); IEnumerable? truncatedHistory = null; if (truncationIndex > 0) { truncatedHistory = chatHistory.Extract(truncationIndex, systemMessage: systemMessage); } return Task.FromResult?>(truncatedHistory); } #region private /// /// Compute the index truncation where truncation should begin using the current truncation threshold. /// /// Chat history to be truncated. /// The system message private int ComputeTruncationIndex(IReadOnlyList chatHistory, ChatMessageContent? systemMessage) { var truncationIndex = -1; var totalTokenCount = (int)(systemMessage?.Metadata?["TokenCount"] ?? 0); for (int i = chatHistory.Count - 1; i >= 0; i--) { truncationIndex = i; var tokenCount = (int)(chatHistory[i].Metadata?["TokenCount"] ?? 0); if (tokenCount + totalTokenCount > this._maxTokenCount) { break; } totalTokenCount += tokenCount; } // Skip function related content while (truncationIndex < chatHistory.Count) { if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent)) { truncationIndex++; } else { break; } } return truncationIndex; } #endregion }