// Copyright (c) Microsoft. All rights reserved. using Microsoft.ML.Tokenizers; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; namespace ChatCompletion; /// /// Extension methods for ."/> /// internal static class ChatHistoryExtensions { private static readonly Tokenizer s_tokenizer = TiktokenTokenizer.CreateForModel("gpt-4"); /// /// Returns the system prompt from the chat history. /// /// /// For simplicity only a single system message is supported in these examples. /// internal static ChatMessageContent? GetSystemMessage(this IReadOnlyList chatHistory) { return chatHistory.FirstOrDefault(m => m.Role == AuthorRole.System); } /// /// Extract a range of messages from the provided . /// /// The source history /// The index of the first messageContent to extract /// The index of the first messageContent to extract, if null extract up to the end of the chat history /// An optional system messageContent to include /// An optional summary messageContent to include /// An optional message filter public static IEnumerable Extract( this IReadOnlyList chatHistory, int startIndex, int? endIndex = null, ChatMessageContent? systemMessage = null, ChatMessageContent? summaryMessage = null, Func? messageFilter = null) { endIndex ??= chatHistory.Count - 1; if (startIndex > endIndex) { yield break; } if (systemMessage is not null) { yield return systemMessage; } if (summaryMessage is not null) { yield return summaryMessage; } for (int index = startIndex; index <= endIndex; ++index) { var messageContent = chatHistory[index]; if (messageFilter?.Invoke(messageContent) ?? false) { continue; } yield return messageContent; } } /// /// Compute the index truncation where truncation should begin using the current truncation threshold. /// /// The source history. /// Truncated size. /// Truncation threshold. /// Flag indicating whether or not the chat history contains a system messageContent public static int ComputeTruncationIndex(this IReadOnlyList chatHistory, int truncatedSize, int truncationThreshold, bool hasSystemMessage) { if (chatHistory.Count <= truncationThreshold) { return -1; } // Compute the index of truncation target var truncationIndex = chatHistory.Count - (truncatedSize - (hasSystemMessage ? 1 : 0)); // Skip function related content while (truncationIndex < chatHistory.Count) { if (chatHistory[truncationIndex].Items.Any(i => i is FunctionCallContent or FunctionResultContent)) { truncationIndex++; } else { break; } } return truncationIndex; } /// /// Add a system messageContent to the chat history /// /// Chat history instance /// Message content public static void AddSystemMessageWithTokenCount(this ChatHistory chatHistory, string content) { IReadOnlyDictionary metadata = new Dictionary { ["TokenCount"] = s_tokenizer.CountTokens(content) }; chatHistory.AddMessage(AuthorRole.System, content, metadata: metadata); } /// /// Add a user messageContent to the chat history /// /// Chat history instance /// Message content public static void AddUserMessageWithTokenCount(this ChatHistory chatHistory, string content) { IReadOnlyDictionary metadata = new Dictionary { ["TokenCount"] = s_tokenizer.CountTokens(content) }; chatHistory.AddMessage(AuthorRole.User, content, metadata: metadata); } /// /// Add a assistant messageContent to the chat history /// /// Chat history instance /// Message content public static void AddAssistantMessageWithTokenCount(this ChatHistory chatHistory, string content) { IReadOnlyDictionary metadata = new Dictionary { ["TokenCount"] = s_tokenizer.CountTokens(content) }; chatHistory.AddMessage(AuthorRole.Assistant, content, metadata: metadata); } }