Skip to content

Commit

Permalink
Add AsChatClient for OpenAI's AssistantClient (#5852)
Browse files Browse the repository at this point in the history
- 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}.
  • Loading branch information
stephentoub authored Feb 10, 2025
1 parent a0688e0 commit ecdc326
Show file tree
Hide file tree
Showing 14 changed files with 676 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public ChatMessage Message
/// <summary>Gets or sets the ID of the chat completion.</summary>
public string? CompletionId { get; set; }

/// <summary>Gets or sets the chat thread ID associated with this chat completion.</summary>
/// <remarks>
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a chat thread, such that
/// the input messages supplied to <see cref="IChatClient.CompleteAsync"/> 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="ChatCompletion"/>'s message) as part of the <c>chatMessages</c> parameter.
/// </remarks>
public string? ChatThreadId { get; set; }

/// <summary>Gets or sets the model ID used in the creation of the chat completion.</summary>
public string? ModelId { get; set; }

Expand Down Expand Up @@ -133,6 +143,7 @@ public StreamingChatCompletionUpdate[] ToStreamingChatCompletionUpdates()
ChatMessage choice = Choices[choiceIndex];
updates[choiceIndex] = new StreamingChatCompletionUpdate
{
ChatThreadId = ChatThreadId,
ChoiceIndex = choiceIndex,

AdditionalProperties = choice.AdditionalProperties,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ public ChatMessage(
_contents = Throw.IfNull(contents);
}

/// <summary>Clones the <see cref="ChatMessage"/> to a new <see cref="ChatMessage"/> instance.</summary>
/// <returns>A shallow clone of the original message object.</returns>
/// <remarks>
/// This is a shallow clone. The returned instance is different from the original, but all properties
/// refer to the same objects as the original.
/// </remarks>
public ChatMessage Clone() =>
new()
{
AdditionalProperties = AdditionalProperties,
_authorName = _authorName,
_contents = _contents,
RawRepresentation = RawRepresentation,
Role = Role,
};

/// <summary>Gets or sets the name of the author of the message.</summary>
public string? AuthorName
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace Microsoft.Extensions.AI;
/// <summary>Represents the options for a chat request.</summary>
public class ChatOptions
{
/// <summary>Gets or sets an optional identifier used to associate a request with an existing chat thread.</summary>
public string? ChatThreadId { get; set; }

/// <summary>Gets or sets the temperature for generating chat responses.</summary>
public float? Temperature { get; set; }

Expand Down Expand Up @@ -72,6 +75,7 @@ public virtual ChatOptions Clone()
{
ChatOptions options = new()
{
ChatThreadId = ChatThreadId,
Temperature = Temperature,
MaxOutputTokens = MaxOutputTokens,
TopP = TopP,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ public IList<AIContent> Contents
/// <summary>Gets or sets the ID of the completion of which this update is a part.</summary>
public string? CompletionId { get; set; }

/// <summary>Gets or sets the chat thread ID associated with the chat completion of which this update is a part.</summary>
/// <remarks>
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a chat thread, such that
/// the input messages supplied to <see cref="IChatClient.CompleteStreamingAsync"/> 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 streaming message) as part of the <c>chatMessages</c> parameter.
/// </remarks>
public string? ChatThreadId { get; set; }

/// <summary>Gets or sets a timestamp for the completion update.</summary>
public DateTimeOffset? CreatedAt { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

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

namespace Microsoft.Extensions.AI;

Expand Down Expand Up @@ -103,7 +104,21 @@ private static void ProcessUpdate(StreamingChatCompletionUpdate update, Dictiona
}
#endif

((List<AIContent>)message.Contents).AddRange(update.Contents);
// Incorporate all content from the update into the completion.
foreach (var content in update.Contents)
{
switch (content)
{
// Usage content is treated specially and propagated to the completion's Usage.
case UsageContent usage:
(completion.Usage ??= new()).Add(usage.Details);
break;

default:
message.Contents.Add(content);
break;
}
}

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

completion.Choices.Add(entry.Value);

if (completion.Usage is null)
{
foreach (var content in entry.Value.Contents)
{
if (content is UsageContent c)
{
completion.Usage = c.Details;
entry.Value.Contents = entry.Value.Contents.ToList();
_ = entry.Value.Contents.Remove(c);
break;
}
}
}
}
}

Expand Down
Loading

0 comments on commit ecdc326

Please sign in to comment.