diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotation.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotation.cs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationExtensions.cs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationKind.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationKind.cs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationReference.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIAnnotationReference.cs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/DataContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/DataContent.cs
index 5bbde1e1444..57f7fac1962 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/DataContent.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/DataContent.cs
@@ -183,6 +183,13 @@ public string Uri
[JsonIgnore]
public string MediaType { get; }
+ /// Gets or sets an optional name associated with the data.
+ ///
+ /// A service might use this name as part of citations or to help infer the type of data
+ /// being represented based on a file extension.
+ ///
+ public string? Name { get; set; }
+
/// Gets the data represented by this instance.
///
/// If the instance was constructed from a , this property returns that data.
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
index b47765269af..4d190fccda8 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
@@ -1349,6 +1349,10 @@
"Member": "System.ReadOnlyMemory Microsoft.Extensions.AI.DataContent.Data { get; }",
"Stage": "Stable"
},
+ {
+ "Member": "string? Microsoft.Extensions.AI.DataContent.Name { get; set; }",
+ "Stage": "Stable"
+ },
{
"Member": "string Microsoft.Extensions.AI.DataContent.MediaType { get; }",
"Stage": "Stable"
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
index 394fccad1b6..52dfc3dc8af 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
@@ -264,7 +264,7 @@ private static List ToOpenAIChatContent(IList
break;
case DataContent dataContent when dataContent.MediaType.StartsWith("application/pdf", StringComparison.OrdinalIgnoreCase):
- return ChatMessageContentPart.CreateFilePart(BinaryData.FromBytes(dataContent.Data), dataContent.MediaType, $"{Guid.NewGuid():N}.pdf");
+ return ChatMessageContentPart.CreateFilePart(BinaryData.FromBytes(dataContent.Data), dataContent.MediaType, dataContent.Name ?? $"{Guid.NewGuid():N}.pdf");
case AIContent when content.RawRepresentation is ChatMessageContentPart rawContentPart:
return rawContentPart;
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs
index c4a1261844c..57ed46721b0 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs
@@ -639,7 +639,7 @@ private static List ToOpenAIResponsesContent(IList([0x01, 0x02, 0x03, 0x04]), "application/octet-stream"),
+ JsonSerializer.Serialize(
+ new DataContent(new ReadOnlyMemory([0x01, 0x02, 0x03, 0x04]), "application/octet-stream"),
+ TestJsonSerializerContext.Default.Options));
+
+ Assert.Equal(
+ """{"uri":"data:application/octet-stream;base64,AQIDBA==","name":"test.bin"}""",
+ JsonSerializer.Serialize(
+ new DataContent(new ReadOnlyMemory([0x01, 0x02, 0x03, 0x04]), "application/octet-stream") { Name = "test.bin" },
TestJsonSerializerContext.Default.Options));
}
@@ -260,4 +267,13 @@ public void NonBase64Data_Normalized()
Assert.Equal("aGVsbG8gd29ybGQ=", content.Base64Data.ToString());
Assert.Equal("hello world", Encoding.ASCII.GetString(content.Data.ToArray()));
}
+
+ [Fact]
+ public void FileName_Roundtrips()
+ {
+ DataContent content = new(new byte[] { 1, 2, 3 }, "application/octet-stream");
+ Assert.Null(content.Name);
+ content.Name = "test.bin";
+ Assert.Equal("test.bin", content.Name);
+ }
}
diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
index ffa94f64531..c9f0e09abf3 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
@@ -202,7 +202,7 @@ public virtual async Task MultiModal_DescribePdf()
new(ChatRole.User,
[
new TextContent("What text does this document contain?"),
- new DataContent(ImageDataUri.GetPdfDataUri(), "application/pdf"),
+ new DataContent(ImageDataUri.GetPdfDataUri(), "application/pdf") { Name = "sample.pdf" },
])
],
new() { ModelId = GetModel_MultiModal_DescribeImage() });