Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
69fd382
Merged PR 49569: Cherry pick Evaluation changes for 9.4.3 release
peterwald Apr 28, 2025
ade41ed
Update package-lock.json for Evaluations
peterwald Apr 28, 2025
3249a8e
Reset package-lock.json to same as main branch
peterwald Apr 28, 2025
4f467ce
Merged PR 49585: [9.4.3] [cherry pick] A couple of minor fixes
Apr 29, 2025
0364636
Add test for optional parameters being required with RequireAllProper…
jozkee Apr 9, 2025
4ef956f
Adding reference to an unsupported built-in tool on OpenAI Chat API n…
artl93 Apr 10, 2025
d58c187
Update M.E.AI changelogs (#6269)
stephentoub Apr 10, 2025
5181d62
Augment UseDistributedCache XML docs (#6256)
stephentoub Apr 14, 2025
d9171c8
Augment AIFunctionFactory.Create XML docs (#6255)
stephentoub Apr 14, 2025
0bb275b
Rename EmbeddingGeneratorExtensions.GenerateEmbedding extension metho…
roji Apr 14, 2025
9d1e3be
Augment FunctionInvokingChatClient's span with token counts (#6296)
stephentoub Apr 15, 2025
18dad04
Rename ChatThreadId to ConversationId (#6300)
SteveSandersonMS Apr 15, 2025
e1dfbb6
Restore deleted members as obsolete (#6304)
jeffhandley Apr 16, 2025
1eae7b6
Support [FromKeyedServices] in AIFunctionFactory (#6310)
stephentoub Apr 17, 2025
bae1a84
Utilize IServiceProviderIsService in AIFunctionFactory (#6317)
stephentoub Apr 18, 2025
cd41d3d
Remove AsChatClient/AsEmbeddingGenerator that were obsoleted in 9.4.0…
jeffhandley Apr 23, 2025
45c6364
Add ChatOptions.AllowMultipleToolCalls (#6326)
SteveSandersonMS Apr 24, 2025
7cc2408
Disable default required property schema generation and OpenAI strict…
eiriktsarpalis Apr 25, 2025
ccf61a7
Merged PR 49624: [9.4.3] [cherry-pick] Update readmes (#6345)
Apr 29, 2025
bc99a9d
Merged PR 49588: Update Microsoft.Extensions.AI and Microsoft.Extensi…
jeffhandley Apr 29, 2025
86cfdf3
Bump version to 9.4.3
jeffhandley Apr 29, 2025
7832e77
Merged PR 49636: [9.4.3] [cherry-pick] Skip messages that have no tex…
Apr 30, 2025
fcf0097
Add test for optional parameters being required with RequireAllProper…
jozkee Apr 9, 2025
b6fcfd1
Adding reference to an unsupported built-in tool on OpenAI Chat API n…
artl93 Apr 10, 2025
9667bfe
Update M.E.AI changelogs (#6269)
stephentoub Apr 10, 2025
8e43dfe
Augment UseDistributedCache XML docs (#6256)
stephentoub Apr 14, 2025
613a256
Augment AIFunctionFactory.Create XML docs (#6255)
stephentoub Apr 14, 2025
689f3a6
Rename EmbeddingGeneratorExtensions.GenerateEmbedding extension metho…
roji Apr 14, 2025
a6a2f4f
Augment FunctionInvokingChatClient's span with token counts (#6296)
stephentoub Apr 15, 2025
a9ff0cc
Rename ChatThreadId to ConversationId (#6300)
SteveSandersonMS Apr 15, 2025
3d8f0c1
Restore deleted members as obsolete (#6304)
jeffhandley Apr 16, 2025
99deb9e
Support [FromKeyedServices] in AIFunctionFactory (#6310)
stephentoub Apr 17, 2025
79a94c6
Utilize IServiceProviderIsService in AIFunctionFactory (#6317)
stephentoub Apr 18, 2025
a5e7a69
Remove AsChatClient/AsEmbeddingGenerator that were obsoleted in 9.4.0…
jeffhandley Apr 23, 2025
24188c7
Add ChatOptions.AllowMultipleToolCalls (#6326)
SteveSandersonMS Apr 24, 2025
d0aa11b
Disable default required property schema generation and OpenAI strict…
eiriktsarpalis Apr 25, 2025
e9b8ef0
Bump version to 9.4.3
jeffhandley Apr 29, 2025
19b5db8
Merged PR 49636: [9.4.3] [cherry-pick] Skip messages that have no tex…
Apr 30, 2025
4a227fb
Update chat template dependencies, fix OpenAI/Aspire config, and addr…
MackinnonBuck Apr 16, 2025
e8422f1
Expose AIContent constructor (#6346)
stephentoub Apr 29, 2025
a6b5496
Add PDF support to OpenAI AsIChatClient (#6344)
stephentoub Apr 29, 2025
b5b2518
Make CreateJsonSchema tolerate JSO inputs that don't have a resolver …
eiriktsarpalis Apr 29, 2025
1ee4daa
Update MEAI.Templates to use the just-built version of the libraries
jeffhandley Apr 30, 2025
801eed1
Merged PR 49653: Update MEAI, MEAI.Abstractions, and MEAI.Templates f…
jeffhandley Apr 30, 2025
458d2c7
Enhance Function Invocation Extensibility for Microsoft.Extensions.AI…
rogerbarreto Apr 30, 2025
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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup Label="Version settings">
<MajorVersion>9</MajorVersion>
<MinorVersion>4</MinorVersion>
<PatchVersion>2</PatchVersion>
<PatchVersion>3</PatchVersion>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
<PreReleaseVersionIteration>1</PreReleaseVersionIteration>
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
Expand Down
1 change: 1 addition & 0 deletions eng/packages/TestOnly.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageVersion Include="Moq.AutoMock" Version="3.1.0" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.9.0" />
<PackageVersion Include="PdfPig" Version="0.1.10-alpha-20250203-fdb88" />
<PackageVersion Include="Polly.Testing" Version="8.4.2" />
<PackageVersion Include="StrongNamer" Version="0.2.5" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="$(SystemConfigurationConfigurationManagerVersion)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Release History

## 9.4.0-preview.1.25207.5

- Added `ErrorContent` and `TextReasoningContent`.
- Added `MessageId` to `ChatMessage` and `ChatResponseUpdate`.
- Added `AIFunctionArguments`, changing `AIFunction.InvokeAsync` to accept one and to return a `ValueTask`.
- Updated `AIJsonUtilities`'s schema generation to not use `default` when `RequireAllProperties` is set to `true`.
- Added `ISpeechToTextClient` and supporting types.
- Fixed several issues related to Native AOT support.

## 9.3.0-preview.1.25161.3

- Changed `IChatClient.GetResponseAsync` and `IChatClient.GetStreamingResponseAsync` to accept an `IEnumerable<ChatMessage>` rather than an `IList<ChatMessage>`. It is no longer mutated by implementations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ 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 an optional identifier used to associate a request with an existing conversation.</summary>
/// <remarks>This property is obsolete. Use <see cref="ConversationId"/> instead.</remarks>
[System.Obsolete("Use ConversationId instead.")]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public string? ChatThreadId
{
get => ConversationId;
set => ConversationId = value;
}

/// <summary>Gets or sets an optional identifier used to associate a request with an existing conversation.</summary>
public string? ConversationId { get; set; }

/// <summary>Gets or sets the temperature for generating chat responses.</summary>
/// <remarks>
Expand Down Expand Up @@ -83,6 +93,23 @@ public class ChatOptions
/// </remarks>
public IList<string>? StopSequences { get; set; }

/// <summary>
/// Gets or sets a flag to indicate whether a single response is allowed to include multiple tool calls.
/// If <see langword="false"/>, the <see cref="IChatClient"/> is asked to return a maximum of one tool call per request.
/// If <see langword="true"/>, there is no limit.
/// If <see langword="null"/>, the provider may select its own default.
/// </summary>
/// <remarks>
/// <para>
/// When used with function calling middleware, this does not affect the ability to perform multiple function calls in sequence.
/// It only affects the number of function calls within a single iteration of the function calling loop.
/// </para>
/// <para>
/// The underlying provider is not guaranteed to support or honor this flag. For example it may choose to ignore it and return multiple tool calls regardless.
/// </para>
/// </remarks>
public bool? AllowMultipleToolCalls { get; set; }

/// <summary>Gets or sets the tool mode for the chat request.</summary>
/// <remarks>The default value is <see langword="null"/>, which is treated the same as <see cref="ChatToolMode.Auto"/>.</remarks>
public ChatToolMode? ToolMode { get; set; }
Expand All @@ -105,7 +132,7 @@ public virtual ChatOptions Clone()
{
ChatOptions options = new()
{
ChatThreadId = ChatThreadId,
ConversationId = ConversationId,
Temperature = Temperature,
MaxOutputTokens = MaxOutputTokens,
TopP = TopP,
Expand All @@ -115,6 +142,7 @@ public virtual ChatOptions Clone()
Seed = Seed,
ResponseFormat = ResponseFormat,
ModelId = ModelId,
AllowMultipleToolCalls = AllowMultipleToolCalls,
ToolMode = ToolMode,
AdditionalProperties = AdditionalProperties?.Clone(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,36 @@ public IList<ChatMessage> Messages
/// <summary>Gets or sets the ID of the chat response.</summary>
public string? ResponseId { get; set; }

/// <summary>Gets or sets the chat thread ID associated with this chat response.</summary>
/// <summary>Gets or sets an identifier for the state of the conversation.</summary>
/// <remarks>
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a chat thread, such that
/// Some <see cref="IChatClient"/> implementations are capable of storing the state for a conversation, such that
/// 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>messages</c> parameter.
/// and it should be used in a subsequent <see cref="ChatOptions.ConversationId"/> instead of supplying the same messages
/// (and this <see cref="ChatResponse"/>'s message) as part of the <c>messages</c> parameter. Note that the value may
/// or may not 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? ChatThreadId { get; set; }
/// <remarks>This method is obsolete. Use <see cref="ConversationId"/> instead.</remarks>
[Obsolete("Use ConversationId instead.")]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public string? ChatThreadId
{
get => ConversationId;
set => ConversationId = value;
}

/// <summary>Gets or sets an identifier for the state of the conversation.</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.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.ConversationId"/> instead of supplying the same messages
/// (and this <see cref="ChatResponse"/>'s message) as part of the <c>messages</c> parameter. Note that the value may
/// or may not 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 the model ID used in the creation of the chat response.</summary>
public string? ModelId { get; set; }
Expand Down Expand Up @@ -127,7 +148,7 @@ public ChatResponseUpdate[] ToChatResponseUpdates()
ChatMessage message = _messages![i];
updates[i] = new ChatResponseUpdate
{
ChatThreadId = ChatThreadId,
ConversationId = ConversationId,

AdditionalProperties = message.AdditionalProperties,
AuthorName = message.AuthorName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ private static void ProcessUpdate(ChatResponseUpdate update, ChatResponse respon
response.ResponseId = update.ResponseId;
}

if (update.ChatThreadId is not null)
if (update.ConversationId is not null)
{
response.ChatThreadId = update.ChatThreadId;
response.ConversationId = update.ConversationId;
}

if (update.CreatedAt is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,34 @@ public IList<AIContent> Contents
/// </remarks>
public string? MessageId { get; set; }

/// <summary>Gets or sets the chat thread ID associated with the chat response of which this update is a part.</summary>
/// <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 chat thread, such that
/// 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.ChatThreadId"/> instead of supplying the same messages
/// (and this streaming message) as part of the <c>messages</c> parameter.
/// 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 may or may not 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? ChatThreadId { get; set; }
/// <remarks>This method is obsolete. Use <see cref="ConversationId"/> instead.</remarks>
[Obsolete("Use ConversationId instead.")]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public string? ChatThreadId
{
get => ConversationId;
set => ConversationId = value;
}

/// <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 may or may not 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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Microsoft.Extensions.AI;

/// <summary>Provides a base class for all content used with AI services.</summary>
/// <summary>Represents content used by AI services.</summary>
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(DataContent), typeDiscriminator: "data")]
[JsonDerivedType(typeof(ErrorContent), typeDiscriminator: "error")]
Expand All @@ -20,15 +20,15 @@ public class AIContent
/// <summary>
/// Initializes a new instance of the <see cref="AIContent"/> class.
/// </summary>
protected AIContent()
public AIContent()
{
}

/// <summary>Gets or sets the raw representation of the content from an underlying implementation.</summary>
/// <remarks>
/// If an <see cref="AIContent"/> 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.
/// for enabling a consumer to access the underlying object model, if needed.
/// </remarks>
[JsonIgnore]
public object? RawRepresentation { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,45 @@ public static TService GetRequiredService<TService>(
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The generator did not produce exactly one embedding.</exception>
/// <remarks>
/// This operation is equivalent to using <see cref="GenerateEmbeddingAsync"/> and returning the
/// This operation is equivalent to using <see cref="GenerateAsync"/> and returning the
/// resulting <see cref="Embedding{T}"/>'s <see cref="Embedding{T}.Vector"/> property.
/// </remarks>
/// <remarks>
/// This method is obsolete. Use <see cref="GenerateVectorAsync{TInput, TEmbeddingElement}"/> instead.
/// </remarks>
[Obsolete("Use GenerateVectorAsync<TInput, TEmbeddingElement> instead.")]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static async Task<ReadOnlyMemory<TEmbeddingElement>> GenerateEmbeddingVectorAsync<TInput, TEmbeddingElement>(
this IEmbeddingGenerator<TInput, Embedding<TEmbeddingElement>> generator,
TInput value,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
var embedding = await GenerateEmbeddingAsync(generator, value, options, cancellationToken).ConfigureAwait(false);
return await GenerateVectorAsync(generator, value, options, cancellationToken).ConfigureAwait(false);
}

/// <summary>Generates an embedding vector from the specified <paramref name="value"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbeddingElement">The numeric type of the embedding data.</typeparam>
/// <param name="generator">The embedding generator.</param>
/// <param name="value">A value from which an embedding will be generated.</param>
/// <param name="options">The embedding generation options to configure the request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The generated embedding for the specified <paramref name="value"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="generator"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The generator did not produce exactly one embedding.</exception>
/// <remarks>
/// This operation is equivalent to using <see cref="GenerateAsync"/> and returning the
/// resulting <see cref="Embedding{T}"/>'s <see cref="Embedding{T}.Vector"/> property.
/// </remarks>
public static async Task<ReadOnlyMemory<TEmbeddingElement>> GenerateVectorAsync<TInput, TEmbeddingElement>(
this IEmbeddingGenerator<TInput, Embedding<TEmbeddingElement>> generator,
TInput value,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
var embedding = await GenerateAsync(generator, value, options, cancellationToken).ConfigureAwait(false);
return embedding.Vector;
}

Expand All @@ -130,12 +159,45 @@ public static async Task<ReadOnlyMemory<TEmbeddingElement>> GenerateEmbeddingVec
/// collection composed of the single <paramref name="value"/> and then returning the first embedding element from the
/// resulting <see cref="GeneratedEmbeddings{TEmbedding}"/> collection.
/// </remarks>
/// <remarks>
/// This method is obsolete. Use <see cref="GenerateAsync{TInput, TEmbedding}"/> instead.
/// </remarks>
[Obsolete("Use GenerateAsync<TInput, TEmbedding> instead.")]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static async Task<TEmbedding> GenerateEmbeddingAsync<TInput, TEmbedding>(
this IEmbeddingGenerator<TInput, TEmbedding> generator,
TInput value,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
where TEmbedding : Embedding
{
return await GenerateAsync(generator, value, options, cancellationToken).ConfigureAwait(false);
}

/// <summary>Generates an embedding from the specified <paramref name="value"/>.</summary>
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
/// <typeparam name="TEmbedding">The type of embedding to generate.</typeparam>
/// <param name="generator">The embedding generator.</param>
/// <param name="value">A value from which an embedding will be generated.</param>
/// <param name="options">The embedding generation options to configure the request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>
/// The generated embedding for the specified <paramref name="value"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="generator"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The generator did not produce exactly one embedding.</exception>
/// <remarks>
/// This operations is equivalent to using <see cref="IEmbeddingGenerator{TInput, TEmbedding}.GenerateAsync"/> with a
/// collection composed of the single <paramref name="value"/> and then returning the first embedding element from the
/// resulting <see cref="GeneratedEmbeddings{TEmbedding}"/> collection.
/// </remarks>
public static async Task<TEmbedding> GenerateAsync<TInput, TEmbedding>(
this IEmbeddingGenerator<TInput, TEmbedding> generator,
TInput value,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
where TEmbedding : Embedding
{
_ = Throw.IfNull(generator);
_ = Throw.IfNull(value);
Expand Down
Loading
Loading