Skip to content

Commit ea69fde

Browse files
committed
Merged PR 55051: Backport MEAI libraries updates into release/10.0
The following PRs are included in this backport: - [MEDI] start producing NuGet packages (dotnet/extensions/#7016) - Update version numbers in AI changelogs (dotnet/extensions/#7008) - [MEDI] Don't stop document processing on enricher error (dotnet/extensions/#7005) - [MEDI] add PackageTags (dotnet/extensions/#7022) - Add MarkItDownMcpReader for MCP server support (dotnet/extensions/#7025) - Image generation tool (dotnet/extensions/#6749) - Make MEAI packages use 10.0 runtime packages (dotnet/extensions/#7028) ---- #### AI description (iteration 1) #### PR Classification This pull request backports multiple MEAI library updates, including new image generation features, refactoring of data ingestion enrichers, removal of legacy exporter code, and updated OpenTelemetry instrumentation. #### PR Summary The changes integrate new image generation tool support into chat clients with corresponding types and integration tests, refactor data ingestion enrichers to use a unified `EnricherOptions` abstraction with batching, and remove outdated JSON schema exporter and nullability helper files while updating OpenTelemetry metrics and project metadata. - `src/Libraries/Microsoft.Extensions.AI`: Added new types (`HostedImageGenerationTool.cs`, `ImageGenerationToolCallContent.cs`, `ImageGenerationToolResultContent.cs`) and integration tests to enable hosted image generation across AI providers. - `src/Libraries/Microsoft.Extensions.DataIngestion`: Refactored enrichers (Sentiment, Keyword, Classification, Summary) to use the new `EnricherOptions` and batching via the `Batching.cs` utility, with updated tests. - Removed legacy schema exporter files (e.g. files under `src/Shared/JsonSchemaExporter/` and `NullabilityInfoContext/`) to clean up unused functionality. - Updated OpenTelemetry instrumentation in OpenAI, Azure AI, Embedding, and SpeechToText clients to align with the latest semantic conventions. - Revised project and package configuration files with updated metadata, preview stage tags, and code quality settings. <!-- GitOpsUserAgent=GitOps.Apps.Server.pullrequestcopilot -->
2 parents 05fe7a5 + d6f343c commit ea69fde

File tree

80 files changed

+3417
-3328
lines changed

Some content is hidden

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

80 files changed

+3417
-3328
lines changed

Directory.Build.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
<Import Project="$(MSBuildThisFileDirectory)\eng\MSBuild\Generators.targets" />
99
<Import Project="$(MSBuildThisFileDirectory)\eng\MSBuild\ProjectStaging.targets" />
1010

11+
<PropertyGroup>
12+
<!-- Workaround https://github.com/dotnet/sdk/issues/51265 - can be removed when updating to .NET 10.0.100 (GA) SDK -->
13+
<RestoreEnablePackagePruning Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">false</RestoreEnablePackagePruning>
14+
</PropertyGroup>
15+
1116
<!-- Warning stuff -->
1217
<PropertyGroup>
1318
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);NETSDK1138;MSB3270</MSBuildWarningsAsMessages>

eng/MSBuild/LegacySupport.props

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<ItemGroup Condition="'$(InjectCompilerFeatureRequiredOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1')">
33
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CompilerFeatureRequiredAttribute\*.cs" LinkBase="LegacySupport\CompilerFeatureRequiredAttribute" />
44
</ItemGroup>
5-
5+
66
<ItemGroup Condition="'$(InjectRequiredMemberOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1')">
77
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\RequiredMemberAttribute\*.cs" LinkBase="LegacySupport\RequiredMemberAttribute" />
88
</ItemGroup>
@@ -47,10 +47,6 @@
4747
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\StringSyntaxAttribute\*.cs" LinkBase="LegacySupport\StringSyntaxAttribute" />
4848
</ItemGroup>
4949

50-
<ItemGroup Condition="'$(InjectJsonSchemaExporterOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0')">
51-
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\JsonSchemaExporter\**\*.cs" LinkBase="Shared\EmptyCollections" />
52-
</ItemGroup>
53-
5450
<ItemGroup Condition="'$(InjectGetOrAddOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0')">
5551
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\GetOrAdd\*.cs" LinkBase="LegacySupport\GetOrAdd" />
5652
</ItemGroup>

eng/packages/General.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="$(MicrosoftExtensionsVectorDataAbstractionsVersion)" />
1919
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
2020
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="$(MicrosoftMLTokenizersVersion)" />
21+
<PackageVersion Include="ModelContextProtocol.Core" Version="0.4.0-preview.3" />
2122
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
2223
<PackageVersion Include="OllamaSharp" Version="5.1.9" />
2324
<PackageVersion Include="OpenAI" Version="2.6.0" />

src/Libraries/Microsoft.Extensions.AI.Abstractions/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Release History
22

3-
## NOT YET RELEASED
3+
## 9.10.2
44

55
- Updated `AIFunctionFactory` to respect `[DisplayName(...)]` on functions as a way to override the function name.
66
- Updated `AIFunctionFactory` to respect `[DefaultValue(...)]` on function parameters as a way to specify default values.

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

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,47 @@ static async Task<ChatResponse> ToChatResponseAsync(
184184
}
185185
}
186186

187+
/// <summary>
188+
/// Coalesces image result content elements in the provided list of <see cref="AIContent"/> items.
189+
/// Unlike other content coalescing methods, this will coalesce non-sequential items based on their Name property,
190+
/// and it will replace earlier items with later ones when duplicates are found.
191+
/// </summary>
192+
private static void CoalesceImageResultContent(IList<AIContent> contents)
193+
{
194+
Dictionary<string, int>? imageResultIndexById = null;
195+
bool hasRemovals = false;
196+
197+
for (int i = 0; i < contents.Count; i++)
198+
{
199+
if (contents[i] is ImageGenerationToolResultContent imageResult && !string.IsNullOrEmpty(imageResult.ImageId))
200+
{
201+
// Check if there's an existing ImageGenerationToolResultContent with the same ImageId to replace
202+
if (imageResultIndexById is null)
203+
{
204+
imageResultIndexById = new(StringComparer.Ordinal);
205+
}
206+
207+
if (imageResultIndexById.TryGetValue(imageResult.ImageId!, out int existingIndex))
208+
{
209+
// Replace the existing imageResult with the new one
210+
contents[existingIndex] = imageResult;
211+
contents[i] = null!; // Mark the current one for removal, then remove in single o(n) pass
212+
hasRemovals = true;
213+
}
214+
else
215+
{
216+
imageResultIndexById[imageResult.ImageId!] = i;
217+
}
218+
}
219+
}
220+
221+
// Remove all of the null slots left over from the coalescing process.
222+
if (hasRemovals)
223+
{
224+
RemoveNullContents(contents);
225+
}
226+
}
227+
187228
/// <summary>Coalesces sequential <see cref="AIContent"/> content elements.</summary>
188229
internal static void CoalesceContent(IList<AIContent> contents)
189230
{
@@ -219,6 +260,8 @@ internal static void CoalesceContent(IList<AIContent> contents)
219260
return content;
220261
});
221262

263+
CoalesceImageResultContent(contents);
264+
222265
Coalesce<DataContent>(
223266
contents,
224267
mergeSingle: false,
@@ -394,29 +437,35 @@ static bool TryAsCoalescable(AIContent content, [NotNullWhen(true)] out TContent
394437
}
395438

396439
// Remove all of the null slots left over from the coalescing process.
397-
if (contents is List<AIContent> contentsList)
398-
{
399-
_ = contentsList.RemoveAll(u => u is null);
400-
}
401-
else
402-
{
403-
int nextSlot = 0;
404-
int contentsCount = contents.Count;
405-
for (int i = 0; i < contentsCount; i++)
406-
{
407-
if (contents[i] is { } content)
408-
{
409-
contents[nextSlot++] = content;
410-
}
411-
}
440+
RemoveNullContents(contents);
441+
}
442+
}
412443

413-
for (int i = contentsCount - 1; i >= nextSlot; i--)
444+
private static void RemoveNullContents<T>(IList<T> contents)
445+
where T : class
446+
{
447+
if (contents is List<AIContent> contentsList)
448+
{
449+
_ = contentsList.RemoveAll(u => u is null);
450+
}
451+
else
452+
{
453+
int nextSlot = 0;
454+
int contentsCount = contents.Count;
455+
for (int i = 0; i < contentsCount; i++)
456+
{
457+
if (contents[i] is { } content)
414458
{
415-
contents.RemoveAt(i);
459+
contents[nextSlot++] = content;
416460
}
461+
}
417462

418-
Debug.Assert(nextSlot == contents.Count, "Expected final count to equal list length.");
463+
for (int i = contentsCount - 1; i >= nextSlot; i--)
464+
{
465+
contents.RemoveAt(i);
419466
}
467+
468+
Debug.Assert(nextSlot == contents.Count, "Expected final count to equal list length.");
420469
}
421470
}
422471

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Microsoft.Extensions.AI;
2020
/// </para>
2121
/// <para>
2222
/// The relationship between <see cref="ChatResponse"/> and <see cref="ChatResponseUpdate"/> is
23-
/// codified in the <see cref="ChatResponseExtensions.ToChatResponseAsync"/> and
23+
/// codified in the <see cref="ChatResponseExtensions.ToChatResponseAsync(IAsyncEnumerable{ChatResponseUpdate}, System.Threading.CancellationToken)"/> and
2424
/// <see cref="ChatResponse.ToChatResponseUpdates"/>, which enable bidirectional conversions
2525
/// between the two. Note, however, that the provided conversions might be lossy, for example, if multiple
2626
/// updates all have different <see cref="RawRepresentation"/> objects whereas there's only one slot for
@@ -58,6 +58,29 @@ public ChatResponseUpdate(ChatRole? role, IList<AIContent>? contents)
5858
_contents = contents;
5959
}
6060

61+
/// <summary>
62+
/// Creates a new ChatResponseUpdate instance that is a copy of the current object.
63+
/// </summary>
64+
/// <remarks>The cloned object is a shallow copy; reference-type properties will reference the same
65+
/// objects as the original. Use this method to duplicate the response update for further modification without
66+
/// affecting the original instance.</remarks>
67+
/// <returns>A new ChatResponseUpdate object with the same property values as the current instance.</returns>
68+
public ChatResponseUpdate Clone() =>
69+
new()
70+
{
71+
AdditionalProperties = AdditionalProperties,
72+
AuthorName = AuthorName,
73+
Contents = Contents,
74+
CreatedAt = CreatedAt,
75+
ConversationId = ConversationId,
76+
FinishReason = FinishReason,
77+
MessageId = MessageId,
78+
ModelId = ModelId,
79+
RawRepresentation = RawRepresentation,
80+
ResponseId = ResponseId,
81+
Role = Role,
82+
};
83+
6184
/// <summary>Gets or sets the name of the author of the response update.</summary>
6285
public string? AuthorName
6386
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.CodeAnalysis;
5+
6+
namespace Microsoft.Extensions.AI;
7+
8+
/// <summary>
9+
/// Represents the invocation of an image generation tool call by a hosted service.
10+
/// </summary>
11+
[Experimental("MEAI001")]
12+
public sealed class ImageGenerationToolCallContent : AIContent
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="ImageGenerationToolCallContent"/> class.
16+
/// </summary>
17+
public ImageGenerationToolCallContent()
18+
{
19+
}
20+
21+
/// <summary>
22+
/// Gets or sets the unique identifier of the image generation item.
23+
/// </summary>
24+
public string? ImageId { get; set; }
25+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
6+
7+
namespace Microsoft.Extensions.AI;
8+
9+
/// <summary>
10+
/// Represents an image generation tool call invocation by a hosted service.
11+
/// </summary>
12+
/// <remarks>
13+
/// This content type represents when a hosted AI service invokes an image generation tool.
14+
/// It is informational only and represents the call itself, not the result.
15+
/// </remarks>
16+
[Experimental("MEAI001")]
17+
public sealed class ImageGenerationToolResultContent : AIContent
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ImageGenerationToolResultContent"/> class.
21+
/// </summary>
22+
public ImageGenerationToolResultContent()
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Gets or sets the unique identifier of the image generation item.
28+
/// </summary>
29+
public string? ImageId { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the generated content items.
33+
/// </summary>
34+
/// <remarks>
35+
/// Content is typically <see cref="DataContent"/> for images streamed from the tool, or <see cref="UriContent"/> for remotely hosted images, but
36+
/// can also be provider-specific content types that represent the generated images.
37+
/// </remarks>
38+
public IList<AIContent>? Outputs { get; set; }
39+
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/Image/ImageGenerationOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ protected ImageGenerationOptions(ImageGenerationOptions? other)
8181
/// </summary>
8282
public ImageGenerationResponseFormat? ResponseFormat { get; set; }
8383

84+
/// <summary>
85+
/// Gets or sets the number of intermediate streaming images to generate.
86+
/// </summary>
87+
public int? StreamingCount { get; set; }
88+
8489
/// <summary>Gets or sets any additional properties associated with the options.</summary>
8590
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
8691

src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<RootNamespace>Microsoft.Extensions.AI</RootNamespace>
55
<Description>Abstractions representing generative AI components.</Description>
66
<Workstream>AI</Workstream>
7+
<ForceLatestDotnetVersions>true</ForceLatestDotnetVersions>
78
</PropertyGroup>
89

910
<PropertyGroup>
@@ -21,21 +22,20 @@
2122

2223
<PropertyGroup>
2324
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
24-
<InjectJsonSchemaExporterOnLegacy>true</InjectJsonSchemaExporterOnLegacy>
2525
<InjectRequiredMemberOnLegacy>true</InjectRequiredMemberOnLegacy>
2626
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2727
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>
2828
<InjectStringSyntaxAttributeOnLegacy>true</InjectStringSyntaxAttributeOnLegacy>
2929
<InjectSystemIndexOnLegacy>true</InjectSystemIndexOnLegacy>
3030
</PropertyGroup>
3131

32-
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
32+
<ItemGroup>
3333
<PackageReference Include="System.Text.Json" />
3434
</ItemGroup>
3535

3636
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
3737
<Reference Include="System.Net.Http" />
3838
<Reference Include="System.ComponentModel.DataAnnotations" />
3939
</ItemGroup>
40-
40+
4141
</Project>

0 commit comments

Comments
 (0)