Skip to content

Commit a0688e0

Browse files
authored
Address some M.E.AI API feedback (#5860)
- Remove DataContent.ContainsData. While ContainsData could be used to avoid lazily-instantiating a `ReadOnlyMemory<byte>` from a data URI, in all cases examined ContainsData was being used to guard accessing Data, in which case Data.HasValue is sufficient. - Make ToolMode optional. We'd previously said that an implementation could use null to imply None if it wanted, but with this being optional we'd want null to be the same as auto, so I added in an explicit null options. That matches as well with what various other client libs do. - Remove IChatClient/IEmbeddingGenerator.Metadata. GetService can be used instead, reducing what an implementer must implement and what's exposed to a consumer. - Add Complete{Streaming}Async for a single message. We have such accelerators in other places but not here, and it provides a natural grow-up from string to ChatMessage to `IList<ChatMessage>`. - Change UsageDetails.XxTokenCount properties from int? to long?. The AdditionalCounts is already based on longs, and in theory the token counts could exceed int, especially in a situation where UsageData is being used to sum many other call data. - Rename GenerateEmbeddingVectorAsync's TEmbedding to TEmbeddingElement. Everywhere else TEmbedding is used where it represents an Embedding, but here it represents a numerical element in an embedding vector. - Remove setters on FunctionCall/ResultContent for required ctor parameters. Having those setters goes against .NET design guidelines. - Remove FunctionResultContent.Name. It's unnecessary and is actually causing us to do more work in places.
1 parent 66bc675 commit a0688e0

File tree

56 files changed

+460
-339
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+460
-339
lines changed

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,25 @@ public static Task<ChatCompletion> CompleteAsync(
4242
_ = Throw.IfNull(client);
4343
_ = Throw.IfNull(chatMessage);
4444

45-
return client.CompleteAsync([new ChatMessage(ChatRole.User, chatMessage)], options, cancellationToken);
45+
return client.CompleteAsync(new ChatMessage(ChatRole.User, chatMessage), options, cancellationToken);
46+
}
47+
48+
/// <summary>Sends a chat message to the model and returns the response messages.</summary>
49+
/// <param name="client">The chat client.</param>
50+
/// <param name="chatMessage">The chat message to send.</param>
51+
/// <param name="options">The chat options to configure the request.</param>
52+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
53+
/// <returns>The response messages generated by the client.</returns>
54+
public static Task<ChatCompletion> CompleteAsync(
55+
this IChatClient client,
56+
ChatMessage chatMessage,
57+
ChatOptions? options = null,
58+
CancellationToken cancellationToken = default)
59+
{
60+
_ = Throw.IfNull(client);
61+
_ = Throw.IfNull(chatMessage);
62+
63+
return client.CompleteAsync([chatMessage], options, cancellationToken);
4664
}
4765

4866
/// <summary>Sends a user chat text message to the model and streams the response messages.</summary>
@@ -60,6 +78,24 @@ public static IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingA
6078
_ = Throw.IfNull(client);
6179
_ = Throw.IfNull(chatMessage);
6280

63-
return client.CompleteStreamingAsync([new ChatMessage(ChatRole.User, chatMessage)], options, cancellationToken);
81+
return client.CompleteStreamingAsync(new ChatMessage(ChatRole.User, chatMessage), options, cancellationToken);
82+
}
83+
84+
/// <summary>Sends a chat message to the model and streams the response messages.</summary>
85+
/// <param name="client">The chat client.</param>
86+
/// <param name="chatMessage">The chat message to send.</param>
87+
/// <param name="options">The chat options to configure the request.</param>
88+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
89+
/// <returns>The response messages generated by the client.</returns>
90+
public static IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
91+
this IChatClient client,
92+
ChatMessage chatMessage,
93+
ChatOptions? options = null,
94+
CancellationToken cancellationToken = default)
95+
{
96+
_ = Throw.IfNull(client);
97+
_ = Throw.IfNull(chatMessage);
98+
99+
return client.CompleteStreamingAsync([chatMessage], options, cancellationToken);
64100
}
65101
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public class ChatOptions
5151
public IList<string>? StopSequences { get; set; }
5252

5353
/// <summary>Gets or sets the tool mode for the chat request.</summary>
54-
public ChatToolMode ToolMode { get; set; } = ChatToolMode.Auto;
54+
/// <remarks>The default value is <see langword="null"/>, which is treated the same as <see cref="ChatToolMode.Auto"/>.</remarks>
55+
public ChatToolMode? ToolMode { get; set; }
5556

5657
/// <summary>Gets or sets the list of tools to include with a chat request.</summary>
5758
[JsonIgnore]

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ namespace Microsoft.Extensions.AI;
99
/// Describes how tools should be selected by a <see cref="IChatClient"/>.
1010
/// </summary>
1111
/// <remarks>
12-
/// The predefined values <see cref="Auto" /> and <see cref="RequireAny"/> are provided.
12+
/// The predefined values <see cref="Auto" />, <see cref="None"/>, and <see cref="RequireAny"/> are provided.
1313
/// To nominate a specific function, use <see cref="RequireSpecific(string)"/>.
1414
/// </remarks>
1515
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
16+
[JsonDerivedType(typeof(NoneChatToolMode), typeDiscriminator: "none")]
1617
[JsonDerivedType(typeof(AutoChatToolMode), typeDiscriminator: "auto")]
1718
[JsonDerivedType(typeof(RequiredChatToolMode), typeDiscriminator: "required")]
1819
#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable
@@ -32,7 +33,19 @@ private protected ChatToolMode()
3233
/// <see cref="ChatOptions.Tools"/> can contain zero or more <see cref="AITool"/>
3334
/// instances, and the <see cref="IChatClient"/> is free to invoke zero or more of them.
3435
/// </remarks>
35-
public static AutoChatToolMode Auto { get; } = new AutoChatToolMode();
36+
public static AutoChatToolMode Auto { get; } = new();
37+
38+
/// <summary>
39+
/// Gets a predefined <see cref="ChatToolMode"/> indicating that tool usage is unsupported.
40+
/// </summary>
41+
/// <remarks>
42+
/// <see cref="ChatOptions.Tools"/> can contain zero or more <see cref="AITool"/>
43+
/// instances, but the <see cref="IChatClient"/> should not request the invocation of
44+
/// any of them. This can be used when the <see cref="IChatClient"/> should know about
45+
/// tools in order to provide information about them or plan out their usage, but should
46+
/// not request the invocation of any of them.
47+
/// </remarks>
48+
public static NoneChatToolMode None { get; } = new();
3649

3750
/// <summary>
3851
/// Gets a predefined <see cref="ChatToolMode"/> indicating that tool usage is required,

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

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,6 @@ public void Dispose()
3737
/// <summary>Gets the inner <see cref="IChatClient" />.</summary>
3838
protected IChatClient InnerClient { get; }
3939

40-
/// <summary>Provides a mechanism for releasing unmanaged resources.</summary>
41-
/// <param name="disposing"><see langword="true"/> if being called from <see cref="Dispose()"/>; otherwise, <see langword="false"/>.</param>
42-
protected virtual void Dispose(bool disposing)
43-
{
44-
if (disposing)
45-
{
46-
InnerClient.Dispose();
47-
}
48-
}
49-
50-
/// <inheritdoc />
51-
public virtual ChatClientMetadata Metadata => InnerClient.Metadata;
52-
5340
/// <inheritdoc />
5441
public virtual Task<ChatCompletion> CompleteAsync(IList<ChatMessage> chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default)
5542
{
@@ -72,4 +59,14 @@ public virtual IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreaming
7259
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
7360
InnerClient.GetService(serviceType, serviceKey);
7461
}
62+
63+
/// <summary>Provides a mechanism for releasing unmanaged resources.</summary>
64+
/// <param name="disposing"><see langword="true"/> if being called from <see cref="Dispose()"/>; otherwise, <see langword="false"/>.</param>
65+
protected virtual void Dispose(bool disposing)
66+
{
67+
if (disposing)
68+
{
69+
InnerClient.Dispose();
70+
}
71+
}
7572
}

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
5353
ChatOptions? options = null,
5454
CancellationToken cancellationToken = default);
5555

56-
/// <summary>Gets metadata that describes the <see cref="IChatClient"/>.</summary>
57-
ChatClientMetadata Metadata { get; }
58-
5956
/// <summary>Asks the <see cref="IChatClient"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
6057
/// <param name="serviceType">The type of object being requested.</param>
6158
/// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
6+
namespace Microsoft.Extensions.AI;
7+
8+
/// <summary>
9+
/// Indicates that an <see cref="IChatClient"/> should not request the invocation of any tools.
10+
/// </summary>
11+
/// <remarks>
12+
/// Use <see cref="ChatToolMode.None"/> to get an instance of <see cref="NoneChatToolMode"/>.
13+
/// </remarks>
14+
[DebuggerDisplay("None")]
15+
public sealed class NoneChatToolMode : ChatToolMode
16+
{
17+
/// <summary>Initializes a new instance of the <see cref="NoneChatToolMode"/> class.</summary>
18+
/// <remarks>Use <see cref="ChatToolMode.None"/> to get an instance of <see cref="NoneChatToolMode"/>.</remarks>
19+
public NoneChatToolMode()
20+
{
21+
} // must exist in support of polymorphic deserialization of a ChatToolMode
22+
23+
/// <inheritdoc/>
24+
public override bool Equals(object? obj) => obj is NoneChatToolMode;
25+
26+
/// <inheritdoc/>
27+
public override int GetHashCode() => typeof(NoneChatToolMode).GetHashCode();
28+
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/DataContent.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,22 +172,13 @@ public string Uri
172172
[JsonPropertyOrder(1)]
173173
public string? MediaType { get; private set; }
174174

175-
/// <summary>
176-
/// Gets a value indicating whether the content contains data rather than only being a reference to data.
177-
/// </summary>
178-
/// <remarks>
179-
/// If the instance is constructed from a <see cref="ReadOnlyMemory{Byte}"/> or from a data URI, this property returns <see langword="true"/>,
180-
/// as the instance actually contains all of the data it represents. If, however, the instance was constructed from another form of URI, one
181-
/// that simply references where the data can be found but doesn't actually contain the data, this property returns <see langword="false"/>.
182-
/// </remarks>
183-
[MemberNotNullWhen(true, nameof(Data))]
184-
[JsonIgnore]
185-
public bool ContainsData => _dataUri is not null || _data is not null;
186-
187175
/// <summary>Gets the data represented by this instance.</summary>
188176
/// <remarks>
189-
/// If <see cref="ContainsData"/> is <see langword="true" />, this property returns the represented data.
190-
/// If <see cref="ContainsData"/> is <see langword="false" />, this property returns <see langword="null" />.
177+
/// If the instance was constructed from a <see cref="ReadOnlyMemory{Byte}"/>, this property returns that data.
178+
/// If the instance was constructed from a data URI, this property the data contained within the data URI.
179+
/// If, however, the instance was constructed from another form of URI, one that simply references where the
180+
/// data can be found but doesn't actually contain the data, this property returns <see langword="null"/>;
181+
/// no attempt is made to retrieve the data from that URI.
191182
/// </remarks>
192183
[JsonIgnore]
193184
public ReadOnlyMemory<byte>? Data

src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionCallContent.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ public sealed class FunctionCallContent : AIContent
2525
[JsonConstructor]
2626
public FunctionCallContent(string callId, string name, IDictionary<string, object?>? arguments = null)
2727
{
28+
CallId = Throw.IfNull(callId);
2829
Name = Throw.IfNull(name);
29-
CallId = callId;
3030
Arguments = arguments;
3131
}
3232

3333
/// <summary>
34-
/// Gets or sets the function call ID.
34+
/// Gets the function call ID.
3535
/// </summary>
36-
public string CallId { get; set; }
36+
public string CallId { get; }
3737

3838
/// <summary>
39-
/// Gets or sets the name of the function requested.
39+
/// Gets the name of the function requested.
4040
/// </summary>
41-
public string Name { get; set; }
41+
public string Name { get; }
4242

4343
/// <summary>
4444
/// Gets or sets the arguments requested to be provided to the function.

src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionResultContent.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,26 @@ public sealed class FunctionResultContent : AIContent
1919
/// Initializes a new instance of the <see cref="FunctionResultContent"/> class.
2020
/// </summary>
2121
/// <param name="callId">The function call ID for which this is the result.</param>
22-
/// <param name="name">The function name that produced the result.</param>
2322
/// <param name="result">
2423
/// <see langword="null"/> if the function returned <see langword="null"/> or was void-returning
2524
/// and thus had no result, or if the function call failed. Typically, however, to provide meaningfully representative
2625
/// information to an AI service, a human-readable representation of those conditions should be supplied.
2726
/// </param>
2827
[JsonConstructor]
29-
public FunctionResultContent(string callId, string name, object? result)
28+
public FunctionResultContent(string callId, object? result)
3029
{
3130
CallId = Throw.IfNull(callId);
32-
Name = Throw.IfNull(name);
3331
Result = result;
3432
}
3533

3634
/// <summary>
37-
/// Gets or sets the ID of the function call for which this is the result.
35+
/// Gets the ID of the function call for which this is the result.
3836
/// </summary>
3937
/// <remarks>
4038
/// If this is the result for a <see cref="FunctionCallContent"/>, this property should contain the same
4139
/// <see cref="FunctionCallContent.CallId"/> value.
4240
/// </remarks>
43-
public string CallId { get; set; }
44-
45-
/// <summary>
46-
/// Gets or sets the name of the function that was called.
47-
/// </summary>
48-
public string Name { get; set; }
41+
public string CallId { get; }
4942

5043
/// <summary>
5144
/// Gets or sets the result of the function call, or a generic error message if the function call failed.

src/Libraries/Microsoft.Extensions.AI.Abstractions/Embeddings/DelegatingEmbeddingGenerator.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,6 @@ public void Dispose()
4040
GC.SuppressFinalize(this);
4141
}
4242

43-
/// <summary>Provides a mechanism for releasing unmanaged resources.</summary>
44-
/// <param name="disposing"><see langword="true"/> if being called from <see cref="Dispose()"/>; otherwise, <see langword="false"/>.</param>
45-
protected virtual void Dispose(bool disposing)
46-
{
47-
if (disposing)
48-
{
49-
InnerGenerator.Dispose();
50-
}
51-
}
52-
53-
/// <inheritdoc />
54-
public virtual EmbeddingGeneratorMetadata Metadata =>
55-
InnerGenerator.Metadata;
56-
5743
/// <inheritdoc />
5844
public virtual Task<GeneratedEmbeddings<TEmbedding>> GenerateAsync(IEnumerable<TInput> values, EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default) =>
5945
InnerGenerator.GenerateAsync(values, options, cancellationToken);
@@ -68,4 +54,14 @@ public virtual Task<GeneratedEmbeddings<TEmbedding>> GenerateAsync(IEnumerable<T
6854
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
6955
InnerGenerator.GetService(serviceType, serviceKey);
7056
}
57+
58+
/// <summary>Provides a mechanism for releasing unmanaged resources.</summary>
59+
/// <param name="disposing"><see langword="true"/> if being called from <see cref="Dispose()"/>; otherwise, <see langword="false"/>.</param>
60+
protected virtual void Dispose(bool disposing)
61+
{
62+
if (disposing)
63+
{
64+
InnerGenerator.Dispose();
65+
}
66+
}
7167
}

0 commit comments

Comments
 (0)