Skip to content

Commit ecdc326

Browse files
authored
Add AsChatClient for OpenAI's AssistantClient (#5852)
- Adds ChatOptions/ChatCompletion/StreamingChatCompletionUpdate.ChatThreadId. - Adds an AsChatClient extension method for creating an IChatClient from an AssistantClient. - Updates FunctionInvokingChatClient to have KeepFunctionCallingMessages be false by default, and to handle ChatThreadId. - Fixes handling of ChatCompletion.Usage in ToChatCompletion{Async}.
1 parent a0688e0 commit ecdc326

File tree

14 files changed

+676
-193
lines changed

14 files changed

+676
-193
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatCompletion.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ public ChatMessage Message
6161
/// <summary>Gets or sets the ID of the chat completion.</summary>
6262
public string? CompletionId { get; set; }
6363

64+
/// <summary>Gets or sets the chat thread ID associated with this chat completion.</summary>
65+
/// <remarks>
66+
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a chat thread, such that
67+
/// the input messages supplied to <see cref="IChatClient.CompleteAsync"/> need only be the additional messages beyond
68+
/// what's already stored. If this property is non-<see langword="null"/>, it represents an identifier for that state,
69+
/// and it should be used in a subsequent <see cref="ChatOptions.ChatThreadId"/> instead of supplying the same messages
70+
/// (and this <see cref="ChatCompletion"/>'s message) as part of the <c>chatMessages</c> parameter.
71+
/// </remarks>
72+
public string? ChatThreadId { get; set; }
73+
6474
/// <summary>Gets or sets the model ID used in the creation of the chat completion.</summary>
6575
public string? ModelId { get; set; }
6676

@@ -133,6 +143,7 @@ public StreamingChatCompletionUpdate[] ToStreamingChatCompletionUpdates()
133143
ChatMessage choice = Choices[choiceIndex];
134144
updates[choiceIndex] = new StreamingChatCompletionUpdate
135145
{
146+
ChatThreadId = ChatThreadId,
136147
ChoiceIndex = choiceIndex,
137148

138149
AdditionalProperties = choice.AdditionalProperties,

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatMessage.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ public ChatMessage(
3939
_contents = Throw.IfNull(contents);
4040
}
4141

42+
/// <summary>Clones the <see cref="ChatMessage"/> to a new <see cref="ChatMessage"/> instance.</summary>
43+
/// <returns>A shallow clone of the original message object.</returns>
44+
/// <remarks>
45+
/// This is a shallow clone. The returned instance is different from the original, but all properties
46+
/// refer to the same objects as the original.
47+
/// </remarks>
48+
public ChatMessage Clone() =>
49+
new()
50+
{
51+
AdditionalProperties = AdditionalProperties,
52+
_authorName = _authorName,
53+
_contents = _contents,
54+
RawRepresentation = RawRepresentation,
55+
Role = Role,
56+
};
57+
4258
/// <summary>Gets or sets the name of the author of the message.</summary>
4359
public string? AuthorName
4460
{

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ namespace Microsoft.Extensions.AI;
99
/// <summary>Represents the options for a chat request.</summary>
1010
public class ChatOptions
1111
{
12+
/// <summary>Gets or sets an optional identifier used to associate a request with an existing chat thread.</summary>
13+
public string? ChatThreadId { get; set; }
14+
1215
/// <summary>Gets or sets the temperature for generating chat responses.</summary>
1316
public float? Temperature { get; set; }
1417

@@ -72,6 +75,7 @@ public virtual ChatOptions Clone()
7275
{
7376
ChatOptions options = new()
7477
{
78+
ChatThreadId = ChatThreadId,
7579
Temperature = Temperature,
7680
MaxOutputTokens = MaxOutputTokens,
7781
TopP = TopP,

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/StreamingChatCompletionUpdate.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ public IList<AIContent> Contents
102102
/// <summary>Gets or sets the ID of the completion of which this update is a part.</summary>
103103
public string? CompletionId { get; set; }
104104

105+
/// <summary>Gets or sets the chat thread ID associated with the chat completion of which this update is a part.</summary>
106+
/// <remarks>
107+
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a chat thread, such that
108+
/// the input messages supplied to <see cref="IChatClient.CompleteStreamingAsync"/> need only be the additional messages beyond
109+
/// what's already stored. If this property is non-<see langword="null"/>, it represents an identifier for that state,
110+
/// and it should be used in a subsequent <see cref="ChatOptions.ChatThreadId"/> instead of supplying the same messages
111+
/// (and this streaming message) as part of the <c>chatMessages</c> parameter.
112+
/// </remarks>
113+
public string? ChatThreadId { get; set; }
114+
105115
/// <summary>Gets or sets a timestamp for the completion update.</summary>
106116
public DateTimeOffset? CreatedAt { get; set; }
107117

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/StreamingChatCompletionUpdateExtensions.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#pragma warning disable S109 // Magic numbers should not be used
1515
#pragma warning disable S127 // "for" loop stop conditions should be invariant
16+
#pragma warning disable S1121 // Assignments should not be made from within sub-expressions
1617

1718
namespace Microsoft.Extensions.AI;
1819

@@ -103,7 +104,21 @@ private static void ProcessUpdate(StreamingChatCompletionUpdate update, Dictiona
103104
}
104105
#endif
105106

106-
((List<AIContent>)message.Contents).AddRange(update.Contents);
107+
// Incorporate all content from the update into the completion.
108+
foreach (var content in update.Contents)
109+
{
110+
switch (content)
111+
{
112+
// Usage content is treated specially and propagated to the completion's Usage.
113+
case UsageContent usage:
114+
(completion.Usage ??= new()).Add(usage.Details);
115+
break;
116+
117+
default:
118+
message.Contents.Add(content);
119+
break;
120+
}
121+
}
107122

108123
message.AuthorName ??= update.AuthorName;
109124
if (update.Role is ChatRole role && message.Role == default)
@@ -178,20 +193,6 @@ static void AddMessage(ChatCompletion completion, bool coalesceContent, KeyValue
178193
}
179194

180195
completion.Choices.Add(entry.Value);
181-
182-
if (completion.Usage is null)
183-
{
184-
foreach (var content in entry.Value.Contents)
185-
{
186-
if (content is UsageContent c)
187-
{
188-
completion.Usage = c.Details;
189-
entry.Value.Contents = entry.Value.Contents.ToList();
190-
_ = entry.Value.Contents.Remove(c);
191-
break;
192-
}
193-
}
194-
}
195196
}
196197
}
197198

0 commit comments

Comments
 (0)