forked from dotnet/extensions
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatResponseUpdate.cs
More file actions
194 lines (171 loc) · 9.36 KB
/
ChatResponseUpdate.cs
File metadata and controls
194 lines (171 loc) · 9.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 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;
namespace Microsoft.Extensions.AI;
/// <summary>
/// Represents a single streaming response chunk from an <see cref="IChatClient"/>.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="ChatResponseUpdate"/> is so named because it represents updates
/// that layer on each other to form a single chat response. Conceptually, this combines the roles of
/// <see cref="ChatResponse"/> and <see cref="ChatMessage"/> in streaming output.
/// </para>
/// <para>
/// The relationship between <see cref="ChatResponse"/> and <see cref="ChatResponseUpdate"/> is
/// codified in the <see cref="ChatResponseExtensions.ToChatResponseAsync(IAsyncEnumerable{ChatResponseUpdate}, System.Threading.CancellationToken)"/> and
/// <see cref="ChatResponse.ToChatResponseUpdates"/>, which enable bidirectional conversions
/// between the two. Note, however, that the provided conversions might be lossy, for example, if multiple
/// updates all have different <see cref="RawRepresentation"/> objects whereas there's only one slot for
/// such an object available in <see cref="ChatResponse.RawRepresentation"/>. Similarly, if different
/// updates provide different values for properties like <see cref="ModelId"/>,
/// only one of the values will be used to populate <see cref="ChatResponse.ModelId"/>.
/// </para>
/// </remarks>
[DebuggerDisplay("[{Role}] {ContentForDebuggerDisplay}{EllipsesForDebuggerDisplay,nq}")]
public class ChatResponseUpdate
{
/// <summary>The response update content items.</summary>
private IList<AIContent>? _contents;
/// <summary>Initializes a new instance of the <see cref="ChatResponseUpdate"/> class.</summary>
[JsonConstructor]
public ChatResponseUpdate()
{
}
/// <summary>Initializes a new instance of the <see cref="ChatResponseUpdate"/> class.</summary>
/// <param name="role">The role of the author of the update.</param>
/// <param name="content">The text content of the update.</param>
public ChatResponseUpdate(ChatRole? role, string? content)
: this(role, content is null ? null : [new TextContent(content)])
{
}
/// <summary>Initializes a new instance of the <see cref="ChatResponseUpdate"/> class.</summary>
/// <param name="role">The role of the author of the update.</param>
/// <param name="contents">The contents of the update.</param>
public ChatResponseUpdate(ChatRole? role, IList<AIContent>? contents)
{
Role = role;
_contents = contents;
}
/// <summary>
/// Creates a new ChatResponseUpdate instance that is a copy of the current object.
/// </summary>
/// <remarks>The cloned object is a shallow copy; reference-type properties will reference the same
/// objects as the original. Use this method to duplicate the response update for further modification without
/// affecting the original instance.</remarks>
/// <returns>A new ChatResponseUpdate object with the same property values as the current instance.</returns>
public ChatResponseUpdate Clone() =>
new()
{
AdditionalProperties = AdditionalProperties,
AuthorName = AuthorName,
Contents = Contents,
CreatedAt = CreatedAt,
ConversationId = ConversationId,
FinishReason = FinishReason,
MessageId = MessageId,
ModelId = ModelId,
RawRepresentation = RawRepresentation,
ResponseId = ResponseId,
Role = Role,
};
/// <summary>Gets or sets the name of the author of the response update.</summary>
public string? AuthorName
{
get;
set => field = string.IsNullOrWhiteSpace(value) ? null : value;
}
/// <summary>Gets or sets the role of the author of the response update.</summary>
public ChatRole? Role { get; set; }
/// <summary>Gets the text of this update.</summary>
/// <remarks>
/// This property concatenates the text of all <see cref="TextContent"/> objects in <see cref="Contents"/>.
/// </remarks>
[JsonIgnore]
public string Text => _contents is not null ? _contents.ConcatText() : string.Empty;
/// <summary>Gets or sets the chat response update content items.</summary>
[AllowNull]
public IList<AIContent> Contents
{
get => _contents ??= [];
set => _contents = value;
}
/// <summary>Gets or sets the raw representation of the response update from an underlying implementation.</summary>
/// <remarks>
/// If a <see cref="ChatResponseUpdate"/> is created to represent some underlying object from another object
/// model, this property can be used to store that original object. This can be useful for debugging or
/// for enabling a consumer to access the underlying object model if needed.
/// </remarks>
[JsonIgnore]
public object? RawRepresentation { get; set; }
/// <summary>Gets or sets additional properties for the update.</summary>
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
/// <summary>Gets or sets the ID of the response of which this update is a part.</summary>
public string? ResponseId { get; set; }
/// <summary>Gets or sets the ID of the message of which this update is a part.</summary>
/// <remarks>
/// A single streaming response might be composed of multiple messages, each of which might be represented
/// by multiple updates. This property is used to group those updates together into messages.
///
/// Some providers might consider streaming responses to be a single message, and in that case
/// the value of this property might be the same as the response ID.
///
/// This value is used when <see cref="ChatResponseExtensions.ToChatResponseAsync(IAsyncEnumerable{ChatResponseUpdate}, System.Threading.CancellationToken)"/>
/// groups <see cref="ChatResponseUpdate"/> instances into <see cref="ChatMessage"/> instances.
/// The value must be unique to each call to the underlying provider, and must be shared by
/// all updates that are part of the same logical message within a streaming response.
/// </remarks>
public string? MessageId { get; set; }
/// <summary>Gets or sets an identifier for the state of the conversation of which this update is a part.</summary>
/// <remarks>
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a conversation, such that
/// the input messages supplied to <see cref="IChatClient.GetStreamingResponseAsync"/> 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.ConversationId"/> instead of supplying the same messages
/// (and this streaming message) as part of the <c>messages</c> parameter. Note that the value might differ on every
/// response, depending on whether the underlying provider uses a fixed ID for each conversation or updates it for each message.
/// </remarks>
public string? ConversationId { get; set; }
/// <summary>Gets or sets a timestamp for the response update.</summary>
public DateTimeOffset? CreatedAt { get; set; }
/// <summary>Gets or sets the finish reason for the operation.</summary>
public ChatFinishReason? FinishReason { get; set; }
/// <summary>Gets or sets the model ID associated with this response update.</summary>
public string? ModelId { get; set; }
/// <inheritdoc/>
public override string ToString() => Text;
/// <summary>Gets or sets the continuation token for resuming the streamed chat response of which this update is a part.</summary>
/// <remarks>
/// <see cref="IChatClient"/> implementations that support background responses return
/// a continuation token on each update if background responses are allowed in <see cref="ChatOptions.AllowBackgroundResponses"/>.
/// However, for the last update, the token will be <see langword="null"/>.
/// <para>
/// This property should be used for stream resumption, where the continuation token of the latest received update should be
/// passed to <see cref="ChatOptions.ContinuationToken"/> on subsequent calls to <see cref="IChatClient.GetStreamingResponseAsync"/>
/// to resume streaming from the point of interruption.
/// </para>
/// </remarks>
[Experimental("MEAI001")]
[JsonIgnore]
public object? ContinuationToken { get; set; }
/// <summary>Gets a <see cref="AIContent"/> object to display in the debugger display.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private AIContent? ContentForDebuggerDisplay
{
get
{
string text = Text;
return
!string.IsNullOrWhiteSpace(text) ? new TextContent(text) :
_contents is { Count: > 0 } ? _contents[0] :
null;
}
}
/// <summary>Gets an indication for the debugger display of whether there's more content.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string EllipsesForDebuggerDisplay => _contents is { Count: > 1 } ? ", ..." : string.Empty;
}