| File: ChatMessageExtensions.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.AI.Evaluation\Microsoft.Extensions.AI.Evaluation.csproj (Microsoft.Extensions.AI.Evaluation) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI.Evaluation; /// <summary> /// Extension methods for <see cref="ChatMessage"/>. /// </summary> public static class ChatMessageExtensions { /// <summary> /// Given a collection of <paramref name="messages"/> representing an LLM chat conversation, returns a /// single <see cref="ChatMessage"/> representing the last <paramref name="userRequest"/> in this conversation. /// </summary> /// <param name="messages"> /// A collection of <see cref="ChatMessage"/>s representing an LLM chat conversation history. /// </param> /// <param name="userRequest"> /// Returns the last <see cref="ChatMessage"/> in the supplied collection of <paramref name="messages"/> if this /// last <see cref="ChatMessage"/> has <see cref="ChatMessage.Role"/> set to <see cref="ChatRole.User"/>; /// <see langword="null" /> otherwise. /// </param> /// <returns> /// <see langword="true"/> if the last <see cref="ChatMessage"/> in the supplied collection of /// <paramref name="messages"/> has <see cref="ChatMessage.Role"/> set to <see cref="ChatRole.User"/>; /// <see langword="false"/> otherwise. /// </returns> public static bool TryGetUserRequest( this IEnumerable<ChatMessage> messages, [NotNullWhen(true)] out ChatMessage? userRequest) { userRequest = messages.LastOrDefault() is ChatMessage lastMessage && lastMessage.Role == ChatRole.User ? lastMessage : null; return userRequest is not null; } /// <summary> /// Decomposes the supplied collection of <paramref name="messages"/> representing an LLM chat conversation into a /// single <see cref="ChatMessage"/> representing the last <paramref name="userRequest"/> in this conversation and /// a collection of <paramref name="remainingMessages"/> representing the rest of the conversation history. /// </summary> /// <param name="messages"> /// A collection of <see cref="ChatMessage"/>s representing an LLM chat conversation history. /// </param> /// <param name="userRequest"> /// Returns the last <see cref="ChatMessage"/> in the supplied collection of <paramref name="messages"/> if this /// last <see cref="ChatMessage"/> has <see cref="ChatMessage.Role"/> set to <see cref="ChatRole.User"/>; /// <see langword="null" /> otherwise. /// </param> /// <param name="remainingMessages"> /// Returns the remaining <see cref="ChatMessage"/>s in the conversation history excluding /// <paramref name="userRequest"/>. /// </param> /// <returns> /// <see langword="true"/> if the last <see cref="ChatMessage"/> in the supplied collection of /// <paramref name="messages"/> has <see cref="ChatMessage.Role"/> set to <see cref="ChatRole.User"/>; /// <see langword="false"/> otherwise. /// </returns> public static bool TryGetUserRequest( this IEnumerable<ChatMessage> messages, [NotNullWhen(true)] out ChatMessage? userRequest, out IReadOnlyList<ChatMessage> remainingMessages) { List<ChatMessage> conversationHistory = [.. messages]; int lastMessageIndex = conversationHistory.Count - 1; if (lastMessageIndex >= 0 && conversationHistory[lastMessageIndex] is ChatMessage lastMessage && lastMessage.Role == ChatRole.User) { userRequest = lastMessage; conversationHistory.RemoveAt(lastMessageIndex); } else { userRequest = null; } remainingMessages = conversationHistory; return userRequest is not null; } /// <summary> /// Renders the supplied <paramref name="message"/> to a <see langword="string"/>. The returned /// <see langword="string"/> can used as part of constructing an evaluation prompt to evaluate a conversation /// that includes the supplied <paramref name="message"/>. /// </summary> /// <remarks> /// <para> /// This function only considers the <see cref="ChatMessage.Text"/> and ignores any <see cref="AIContent"/>s /// (present within the <see cref="ChatMessage.Contents"/> of the <paramref name="message"/>) that are not /// <see cref="TextContent"/>s. If the <paramref name="message"/> does not contain any <see cref="TextContent"/>s /// then this function returns an empty string. /// </para> /// <para> /// The returned string is prefixed with the <see cref="ChatMessage.Role"/> and /// <see cref="ChatMessage.AuthorName"/> (if available). The returned string also always has a new line character /// at the end. /// </para> /// </remarks> /// <param name="message">The <see cref="ChatMessage"/> that is to be rendered.</param> /// <returns>A <see langword="string"/> containing the rendered <paramref name="message"/>.</returns> public static string RenderText(this ChatMessage message) { _ = Throw.IfNull(message); if (!message.Contents.OfType<TextContent>().Any()) { // Don't render messages (such as messages with role ChatRole.Tool) that don't contain any textual content. return string.Empty; } string? author = message.AuthorName; string role = message.Role.Value; string? content = message.Text; return string.IsNullOrWhiteSpace(author) ? $"[{role}] {content}\n" : $"[{author} ({role})] {content}\n"; } /// <summary> /// Renders the supplied <paramref name="messages"/> to a <see langword="string"/>.The returned /// <see langword="string"/> can used as part of constructing an evaluation prompt to evaluate a conversation /// that includes the supplied <paramref name="messages"/>. /// </summary> /// <remarks> /// <para> /// This function only considers the <see cref="ChatMessage.Text"/> and ignores any <see cref="AIContent"/>s /// (present within the <see cref="ChatMessage.Contents"/> of the <paramref name="messages"/>) that are not /// <see cref="TextContent"/>s. Any <paramref name="messages"/> that contain no <see cref="TextContent"/>s will be /// skipped and will not be rendered. If none of the <paramref name="messages"/> include any /// <see cref="TextContent"/>s then this function will return an empty string. /// </para> /// <para> /// The rendered <paramref name="messages"/> are each prefixed with the <see cref="ChatMessage.Role"/> and /// <see cref="ChatMessage.AuthorName"/> (if available) in the returned string. The rendered /// <see cref="ChatMessage"/>s are also always separated by new line characters in the returned string. /// </para> /// </remarks> /// <param name="messages">The <see cref="ChatMessage"/>s that are to be rendered.</param> /// <returns>A <see langword="string"/> containing the rendered <paramref name="messages"/>.</returns> public static string RenderText(this IEnumerable<ChatMessage> messages) { _ = Throw.IfNull(messages); var builder = new StringBuilder(); foreach (ChatMessage message in messages) { string renderedMessage = message.RenderText(); _ = builder.Append(renderedMessage); } return builder.ToString(); } }