Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@ public bool TryGetValue<T>(string key, [NotNullWhen(true)] out T? value)
/// <inheritdoc />
bool IReadOnlyDictionary<string, TValue>.TryGetValue(string key, out TValue value) => _dictionary.TryGetValue(key, out value!);

/// <summary>Copies all of the entries from <paramref name="items"/> into the dictionary, overwriting any existing items in the dictionary with the same key.</summary>
/// <param name="items">The items to add.</param>
internal void SetAll(IEnumerable<KeyValuePair<string, TValue>> items)
{
_ = Throw.IfNull(items);

foreach (var item in items)
{
_dictionary[item.Key] = item.Value;
}
}

/// <summary>Enumerates the elements of an <see cref="AdditionalPropertiesDictionary{TValue}"/>.</summary>
public struct Enumerator : IEnumerator<KeyValuePair<string, TValue>>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

Expand All @@ -17,14 +17,15 @@ public class ChatMessage
private string? _authorName;

/// <summary>Initializes a new instance of the <see cref="ChatMessage"/> class.</summary>
/// <remarks>The instance defaults to having a role of <see cref="ChatRole.User"/>.</remarks>
[JsonConstructor]
public ChatMessage()
{
}

/// <summary>Initializes a new instance of the <see cref="ChatMessage"/> class.</summary>
/// <param name="role">The role of the author of the message.</param>
/// <param name="content">The contents of the message.</param>
/// <param name="content">The text content of the message.</param>
public ChatMessage(ChatRole role, string? content)
: this(role, content is null ? [] : [new TextContent(content)])
{
Expand All @@ -33,12 +34,10 @@ public ChatMessage(ChatRole role, string? content)
/// <summary>Initializes a new instance of the <see cref="ChatMessage"/> class.</summary>
/// <param name="role">The role of the author of the message.</param>
/// <param name="contents">The contents for this message.</param>
public ChatMessage(
ChatRole role,
IList<AIContent> contents)
public ChatMessage(ChatRole role, IList<AIContent>? contents)
{
Role = role;
_contents = Throw.IfNull(contents);
_contents = contents;
}

/// <summary>Clones the <see cref="ChatMessage"/> to a new <see cref="ChatMessage"/> instance.</summary>
Expand Down Expand Up @@ -67,29 +66,12 @@ public string? AuthorName
/// <summary>Gets or sets the role of the author of the message.</summary>
public ChatRole Role { get; set; } = ChatRole.User;

/// <summary>
/// Gets or sets the text of the first <see cref="TextContent"/> instance in <see cref="Contents" />.
/// </summary>
/// <summary>Gets the text of this message.</summary>
/// <remarks>
/// If there is no <see cref="TextContent"/> instance in <see cref="Contents" />, then the getter returns <see langword="null" />,
/// and the setter adds a new <see cref="TextContent"/> instance with the provided value.
/// This property concatenates the text of all <see cref="TextContent"/> objects in <see cref="Contents"/>.
/// </remarks>
[JsonIgnore]
public string? Text
{
get => Contents.FindFirst<TextContent>()?.Text;
set
{
if (Contents.FindFirst<TextContent>() is { } textContent)
{
textContent.Text = value;
}
else if (value is not null)
{
Contents.Add(new TextContent(value));
}
}
}
public string Text => Contents.ConcatText();

/// <summary>Gets or sets the chat message content items.</summary>
[AllowNull]
Expand All @@ -112,7 +94,7 @@ public IList<AIContent> Contents
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

/// <inheritdoc/>
public override string ToString() => Contents.ConcatText();
public override string ToString() => Text;

/// <summary>Gets a <see cref="AIContent"/> object to display in the debugger display.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,62 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>Represents the response to a chat request.</summary>
/// <remarks>
/// <see cref="ChatResponse"/> provides one or more response messages and metadata about the response.
/// A typical response will contain a single message, however a response may contain multiple messages
/// in a variety of scenarios. For example, if automatic function calling is employed, such that a single
/// request to a <see cref="IChatClient"/> may actually generate multiple roundtrips to an inner <see cref="IChatClient"/>
/// it uses, all of the involved messages may be surfaced as part of the final <see cref="ChatResponse"/>.
/// </remarks>
public class ChatResponse
{
/// <summary>The list of choices in the response.</summary>
private IList<ChatMessage> _choices;
/// <summary>The response messages.</summary>
private IList<ChatMessage>? _messages;

/// <summary>Initializes a new instance of the <see cref="ChatResponse"/> class.</summary>
/// <param name="choices">The list of choices in the response, one message per choice.</param>
[JsonConstructor]
public ChatResponse(IList<ChatMessage> choices)
public ChatResponse()
{
_choices = Throw.IfNull(choices);
}

/// <summary>Initializes a new instance of the <see cref="ChatResponse"/> class.</summary>
/// <param name="message">The chat message representing the singular choice in the response.</param>
/// <param name="message">The response message.</param>
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
public ChatResponse(ChatMessage message)
{
_ = Throw.IfNull(message);
_choices = [message];

Messages.Add(message);
}

/// <summary>Initializes a new instance of the <see cref="ChatResponse"/> class.</summary>
/// <param name="messages">The response messages.</param>
public ChatResponse(IList<ChatMessage>? messages)
{
_messages = messages;
}

/// <summary>Gets or sets the list of chat response choices.</summary>
public IList<ChatMessage> Choices
/// <summary>Gets or sets the chat response messages.</summary>
[AllowNull]
public IList<ChatMessage> Messages
{
get => _choices;
set => _choices = Throw.IfNull(value);
get => _messages ??= new List<ChatMessage>(1);
set => _messages = value;
}

/// <summary>Gets the chat response message.</summary>
/// <summary>Gets the text of the response.</summary>
/// <remarks>
/// If there are multiple choices, this property returns the first choice.
/// If <see cref="Choices"/> is empty, this property will throw. Use <see cref="Choices"/> to access all choices directly.
/// This property concatenates the <see cref="ChatMessage.Text"/> of all <see cref="ChatMessage"/>
/// instances in <see cref="Messages"/>.
/// </remarks>
[JsonIgnore]
public ChatMessage Message
{
get
{
var choices = Choices;
if (choices.Count == 0)
{
throw new InvalidOperationException($"The {nameof(ChatResponse)} instance does not contain any {nameof(ChatMessage)} choices.");
}

return choices[0];
}
}
public string Text => _messages?.ConcatText() ?? string.Empty;

/// <summary>Gets or sets the ID of the chat response.</summary>
public string? ResponseId { get; set; }
Expand All @@ -67,7 +69,7 @@ public ChatMessage Message
/// the input messages supplied to <see cref="IChatClient.GetResponseAsync"/> need only be the additional messages beyond
/// what's already stored. If this property is non-<see langword="null"/>, it represents an identifier for that state,
/// and it should be used in a subsequent <see cref="ChatOptions.ChatThreadId"/> instead of supplying the same messages
/// (and this <see cref="ChatResponse"/>'s message) as part of the <c>chatMessages</c> parameter.
/// (and this <see cref="ChatResponse"/>'s message) as part of the <c>messages</c> parameter.
/// </remarks>
public string? ChatThreadId { get; set; }

Expand Down Expand Up @@ -96,26 +98,7 @@ public ChatMessage Message
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

/// <inheritdoc />
public override string ToString()
{
if (Choices.Count == 1)
{
return Choices[0].ToString();
}

StringBuilder sb = new();
for (int i = 0; i < Choices.Count; i++)
{
if (i > 0)
{
_ = sb.AppendLine().AppendLine();
}

_ = sb.Append("Choice ").Append(i).AppendLine(":").Append(Choices[i]);
}

return sb.ToString();
}
public override string ToString() => Text;

/// <summary>Creates an array of <see cref="ChatResponseUpdate" /> instances that represent this <see cref="ChatResponse" />.</summary>
/// <returns>An array of <see cref="ChatResponseUpdate" /> instances that may be used to represent this <see cref="ChatResponse" />.</returns>
Expand All @@ -135,22 +118,22 @@ public ChatResponseUpdate[] ToChatResponseUpdates()
}
}

int choicesCount = Choices.Count;
var updates = new ChatResponseUpdate[choicesCount + (extra is null ? 0 : 1)];
int messageCount = _messages?.Count ?? 0;
var updates = new ChatResponseUpdate[messageCount + (extra is not null ? 1 : 0)];

for (int choiceIndex = 0; choiceIndex < choicesCount; choiceIndex++)
int i;
for (i = 0; i < messageCount; i++)
{
ChatMessage choice = Choices[choiceIndex];
updates[choiceIndex] = new ChatResponseUpdate
ChatMessage message = _messages![i];
updates[i] = new ChatResponseUpdate
{
ChatThreadId = ChatThreadId,
ChoiceIndex = choiceIndex,

AdditionalProperties = choice.AdditionalProperties,
AuthorName = choice.AuthorName,
Contents = choice.Contents,
RawRepresentation = choice.RawRepresentation,
Role = choice.Role,
AdditionalProperties = message.AdditionalProperties,
AuthorName = message.AuthorName,
Contents = message.Contents,
RawRepresentation = message.RawRepresentation,
Role = message.Role,

ResponseId = ResponseId,
CreatedAt = CreatedAt,
Expand All @@ -161,7 +144,7 @@ public ChatResponseUpdate[] ToChatResponseUpdates()

if (extra is not null)
{
updates[choicesCount] = extra;
updates[i] = extra;
}

return updates;
Expand Down
Loading
Loading