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 32faa8a1f4d..b0b47c4ef6a 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
@@ -2236,6 +2236,14 @@
{
"Member": "long? Microsoft.Extensions.AI.UsageDetails.TotalTokenCount { get; set; }",
"Stage": "Stable"
+ },
+ {
+ "Member": "long? Microsoft.Extensions.AI.UsageDetails.CachedInputTokenCount { get; set; }",
+ "Stage": "Stable"
+ },
+ {
+ "Member": "long? Microsoft.Extensions.AI.UsageDetails.ReasoningTokenCount { get; set; }",
+ "Stage": "Stable"
}
]
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/UsageDetails.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/UsageDetails.cs
index b3c62cb67e0..b3edbad5e99 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/UsageDetails.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/UsageDetails.cs
@@ -21,6 +21,23 @@ public class UsageDetails
/// Gets or sets the total number of tokens used to produce the response.
public long? TotalTokenCount { get; set; }
+ ///
+ /// Gets or sets the number of input tokens that were read from a cache.
+ ///
+ ///
+ /// Cached input tokens should be counted as part of .
+ ///
+ public long? CachedInputTokenCount { get; set; }
+
+ ///
+ /// Gets or sets the number of "reasoning" / "thinking" tokens used internally
+ /// by the model.
+ ///
+ ///
+ /// Reasoning tokens should be counted as part of .
+ ///
+ public long? ReasoningTokenCount { get; set; }
+
/// Gets or sets a dictionary of additional usage counts.
///
/// All values set here are assumed to be summable. For example, when middleware makes multiple calls to an underlying
@@ -38,6 +55,8 @@ public void Add(UsageDetails usage)
InputTokenCount = NullableSum(InputTokenCount, usage.InputTokenCount);
OutputTokenCount = NullableSum(OutputTokenCount, usage.OutputTokenCount);
TotalTokenCount = NullableSum(TotalTokenCount, usage.TotalTokenCount);
+ CachedInputTokenCount = NullableSum(CachedInputTokenCount, usage.CachedInputTokenCount);
+ ReasoningTokenCount = NullableSum(ReasoningTokenCount, usage.ReasoningTokenCount);
if (usage.AdditionalCounts is { } countsToAdd)
{
@@ -80,6 +99,16 @@ internal string DebuggerDisplay
parts.Add($"{nameof(TotalTokenCount)} = {total}");
}
+ if (CachedInputTokenCount is { } cached)
+ {
+ parts.Add($"{nameof(CachedInputTokenCount)} = {cached}");
+ }
+
+ if (ReasoningTokenCount is { } reasoning)
+ {
+ parts.Add($"{nameof(ReasoningTokenCount)} = {reasoning}");
+ }
+
if (AdditionalCounts is { } additionalCounts)
{
foreach (var entry in additionalCounts)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
index 70ef6674bd4..a7ca8c08d95 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
@@ -644,6 +644,8 @@ private static UsageDetails FromOpenAIUsage(ChatTokenUsage tokenUsage)
InputTokenCount = tokenUsage.InputTokenCount,
OutputTokenCount = tokenUsage.OutputTokenCount,
TotalTokenCount = tokenUsage.TotalTokenCount,
+ CachedInputTokenCount = tokenUsage.InputTokenDetails?.CachedTokenCount,
+ ReasoningTokenCount = tokenUsage.OutputTokenDetails?.ReasoningTokenCount,
AdditionalCounts = [],
};
@@ -653,13 +655,11 @@ private static UsageDetails FromOpenAIUsage(ChatTokenUsage tokenUsage)
{
const string InputDetails = nameof(ChatTokenUsage.InputTokenDetails);
counts.Add($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.AudioTokenCount)}", inputDetails.AudioTokenCount);
- counts.Add($"{InputDetails}.{nameof(ChatInputTokenUsageDetails.CachedTokenCount)}", inputDetails.CachedTokenCount);
}
if (tokenUsage.OutputTokenDetails is ChatOutputTokenUsageDetails outputDetails)
{
const string OutputDetails = nameof(ChatTokenUsage.OutputTokenDetails);
- counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.ReasoningTokenCount)}", outputDetails.ReasoningTokenCount);
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AudioTokenCount)}", outputDetails.AudioTokenCount);
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.AcceptedPredictionTokenCount)}", outputDetails.AcceptedPredictionTokenCount);
counts.Add($"{OutputDetails}.{nameof(ChatOutputTokenUsageDetails.RejectedPredictionTokenCount)}", outputDetails.RejectedPredictionTokenCount);
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index eb39754d5fd..1c172db283a 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -1143,19 +1143,9 @@ static FunctionCallOutputResponseItem SerializeAIContent(string callId, IEnumera
InputTokenCount = usage.InputTokenCount,
OutputTokenCount = usage.OutputTokenCount,
TotalTokenCount = usage.TotalTokenCount,
+ CachedInputTokenCount = usage.InputTokenDetails?.CachedTokenCount,
+ ReasoningTokenCount = usage.OutputTokenDetails?.ReasoningTokenCount,
};
-
- if (usage.InputTokenDetails is { } inputDetails)
- {
- ud.AdditionalCounts ??= [];
- ud.AdditionalCounts.Add($"{nameof(usage.InputTokenDetails)}.{nameof(inputDetails.CachedTokenCount)}", inputDetails.CachedTokenCount);
- }
-
- if (usage.OutputTokenDetails is { } outputDetails)
- {
- ud.AdditionalCounts ??= [];
- ud.AdditionalCounts.Add($"{nameof(usage.OutputTokenDetails)}.{nameof(outputDetails.ReasoningTokenCount)}", outputDetails.ReasoningTokenCount);
- }
}
return ud;
diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/UsageContentTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/UsageContentTests.cs
index ed268176c5d..d3bf0889821 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/UsageContentTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/UsageContentTests.cs
@@ -66,7 +66,9 @@ public void Serialization_Roundtrips()
{
InputTokenCount = 10,
OutputTokenCount = 20,
- TotalTokenCount = 30
+ TotalTokenCount = 30,
+ CachedInputTokenCount = 5,
+ ReasoningTokenCount = 8
});
var json = JsonSerializer.Serialize(content, AIJsonUtilities.DefaultOptions);
@@ -77,5 +79,7 @@ public void Serialization_Roundtrips()
Assert.Equal(content.Details.InputTokenCount, deserializedContent.Details.InputTokenCount);
Assert.Equal(content.Details.OutputTokenCount, deserializedContent.Details.OutputTokenCount);
Assert.Equal(content.Details.TotalTokenCount, deserializedContent.Details.TotalTokenCount);
+ Assert.Equal(content.Details.CachedInputTokenCount, deserializedContent.Details.CachedInputTokenCount);
+ Assert.Equal(content.Details.ReasoningTokenCount, deserializedContent.Details.ReasoningTokenCount);
}
}
diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/UsageDetailsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/UsageDetailsTests.cs
new file mode 100644
index 00000000000..d7fcd2545f0
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/UsageDetailsTests.cs
@@ -0,0 +1,190 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Text.Json;
+using Xunit;
+
+namespace Microsoft.Extensions.AI;
+
+public class UsageDetailsTests
+{
+ [Fact]
+ public void Constructor_PropsDefault()
+ {
+ UsageDetails details = new();
+ Assert.Null(details.InputTokenCount);
+ Assert.Null(details.OutputTokenCount);
+ Assert.Null(details.TotalTokenCount);
+ Assert.Null(details.CachedInputTokenCount);
+ Assert.Null(details.ReasoningTokenCount);
+ Assert.Null(details.AdditionalCounts);
+ }
+
+ [Fact]
+ public void Properties_Roundtrip()
+ {
+ UsageDetails details = new()
+ {
+ InputTokenCount = 10,
+ OutputTokenCount = 20,
+ TotalTokenCount = 30,
+ CachedInputTokenCount = 5,
+ ReasoningTokenCount = 8,
+ AdditionalCounts = new() { ["custom"] = 100 }
+ };
+
+ Assert.Equal(10, details.InputTokenCount);
+ Assert.Equal(20, details.OutputTokenCount);
+ Assert.Equal(30, details.TotalTokenCount);
+ Assert.Equal(5, details.CachedInputTokenCount);
+ Assert.Equal(8, details.ReasoningTokenCount);
+ Assert.NotNull(details.AdditionalCounts);
+ Assert.Equal(100, details.AdditionalCounts["custom"]);
+ }
+
+ [Fact]
+ public void Add_NullUsage_Throws()
+ {
+ UsageDetails details = new();
+ Assert.Throws("usage", () => details.Add(null!));
+ }
+
+ [Fact]
+ public void Add_SumsAllProperties()
+ {
+ UsageDetails details1 = new()
+ {
+ InputTokenCount = 10,
+ OutputTokenCount = 20,
+ TotalTokenCount = 30,
+ CachedInputTokenCount = 5,
+ ReasoningTokenCount = 8,
+ };
+
+ UsageDetails details2 = new()
+ {
+ InputTokenCount = 15,
+ OutputTokenCount = 25,
+ TotalTokenCount = 40,
+ CachedInputTokenCount = 7,
+ ReasoningTokenCount = 12,
+ };
+
+ details1.Add(details2);
+
+ Assert.Equal(25, details1.InputTokenCount);
+ Assert.Equal(45, details1.OutputTokenCount);
+ Assert.Equal(70, details1.TotalTokenCount);
+ Assert.Equal(12, details1.CachedInputTokenCount);
+ Assert.Equal(20, details1.ReasoningTokenCount);
+ }
+
+ [Fact]
+ public void Add_WithNullValues_HandlesCorrectly()
+ {
+ UsageDetails details1 = new()
+ {
+ InputTokenCount = 10,
+ CachedInputTokenCount = 5,
+ };
+
+ UsageDetails details2 = new()
+ {
+ OutputTokenCount = 25,
+ ReasoningTokenCount = 12,
+ };
+
+ details1.Add(details2);
+
+ Assert.Equal(10, details1.InputTokenCount);
+ Assert.Equal(25, details1.OutputTokenCount);
+ Assert.Null(details1.TotalTokenCount);
+ Assert.Equal(5, details1.CachedInputTokenCount);
+ Assert.Equal(12, details1.ReasoningTokenCount);
+ }
+
+ [Fact]
+ public void Add_FromNullToValue_SetsValue()
+ {
+ UsageDetails details1 = new();
+
+ UsageDetails details2 = new()
+ {
+ CachedInputTokenCount = 5,
+ ReasoningTokenCount = 10,
+ };
+
+ details1.Add(details2);
+
+ Assert.Equal(5, details1.CachedInputTokenCount);
+ Assert.Equal(10, details1.ReasoningTokenCount);
+ }
+
+ [Fact]
+ public void Add_AdditionalCounts_MergesCorrectly()
+ {
+ UsageDetails details1 = new()
+ {
+ AdditionalCounts = new() { ["key1"] = 10, ["key2"] = 20 }
+ };
+
+ UsageDetails details2 = new()
+ {
+ AdditionalCounts = new() { ["key2"] = 30, ["key3"] = 40 }
+ };
+
+ details1.Add(details2);
+
+ Assert.NotNull(details1.AdditionalCounts);
+ Assert.Equal(10, details1.AdditionalCounts["key1"]);
+ Assert.Equal(50, details1.AdditionalCounts["key2"]);
+ Assert.Equal(40, details1.AdditionalCounts["key3"]);
+ }
+
+ [Fact]
+ public void Serialization_Roundtrips()
+ {
+ UsageDetails details = new()
+ {
+ InputTokenCount = 10,
+ OutputTokenCount = 20,
+ TotalTokenCount = 30,
+ CachedInputTokenCount = 5,
+ ReasoningTokenCount = 8,
+ AdditionalCounts = new() { ["custom"] = 100 }
+ };
+
+ string json = JsonSerializer.Serialize(details, AIJsonUtilities.DefaultOptions);
+ UsageDetails? deserialized = JsonSerializer.Deserialize(json, AIJsonUtilities.DefaultOptions);
+
+ Assert.NotNull(deserialized);
+ Assert.Equal(details.InputTokenCount, deserialized.InputTokenCount);
+ Assert.Equal(details.OutputTokenCount, deserialized.OutputTokenCount);
+ Assert.Equal(details.TotalTokenCount, deserialized.TotalTokenCount);
+ Assert.Equal(details.CachedInputTokenCount, deserialized.CachedInputTokenCount);
+ Assert.Equal(details.ReasoningTokenCount, deserialized.ReasoningTokenCount);
+ Assert.NotNull(deserialized.AdditionalCounts);
+ Assert.Equal(100, deserialized.AdditionalCounts["custom"]);
+ }
+
+ [Fact]
+ public void Serialization_WithNullProperties_Roundtrips()
+ {
+ UsageDetails details = new()
+ {
+ InputTokenCount = 10,
+ OutputTokenCount = 20,
+ };
+
+ string json = JsonSerializer.Serialize(details, AIJsonUtilities.DefaultOptions);
+ UsageDetails? deserialized = JsonSerializer.Deserialize(json, AIJsonUtilities.DefaultOptions);
+
+ Assert.NotNull(deserialized);
+ Assert.Equal(10, deserialized.InputTokenCount);
+ Assert.Equal(20, deserialized.OutputTokenCount);
+ Assert.Null(deserialized.TotalTokenCount);
+ Assert.Null(deserialized.CachedInputTokenCount);
+ Assert.Null(deserialized.ReasoningTokenCount);
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
index 5e4932c6736..458256523e4 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
@@ -171,11 +171,11 @@ public async Task BasicRequestResponse_NonStreaming()
Assert.Equal(8, response.Usage.InputTokenCount);
Assert.Equal(9, response.Usage.OutputTokenCount);
Assert.Equal(17, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -258,12 +258,12 @@ public async Task BasicRequestResponse_Streaming()
Assert.Equal(8, usage.Details.InputTokenCount);
Assert.Equal(9, usage.Details.OutputTokenCount);
Assert.Equal(17, usage.Details.TotalTokenCount);
+ Assert.Equal(5, usage.Details.CachedInputTokenCount);
+ Assert.Equal(90, usage.Details.ReasoningTokenCount);
Assert.Equal(new AdditionalPropertiesDictionary
{
{ "InputTokenDetails.AudioTokenCount", 123 },
- { "InputTokenDetails.CachedTokenCount", 5 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 456 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -845,11 +845,11 @@ public async Task MultipleMessages_NonStreaming()
Assert.Equal(42, response.Usage.InputTokenCount);
Assert.Equal(15, response.Usage.OutputTokenCount);
Assert.Equal(57, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 123 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 456 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -942,11 +942,11 @@ public async Task MultiPartSystemMessage_NonStreaming()
Assert.Equal(42, response.Usage.InputTokenCount);
Assert.Equal(15, response.Usage.OutputTokenCount);
Assert.Equal(57, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1040,11 +1040,11 @@ public async Task EmptyAssistantMessage_NonStreaming()
Assert.Equal(42, response.Usage.InputTokenCount);
Assert.Equal(15, response.Usage.OutputTokenCount);
Assert.Equal(57, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1151,12 +1151,12 @@ public async Task FunctionCallContent_NonStreaming()
Assert.Equal(61, response.Usage.InputTokenCount);
Assert.Equal(16, response.Usage.OutputTokenCount);
Assert.Equal(77, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1235,12 +1235,12 @@ public async Task UnavailableBuiltInFunctionCall_NonStreaming()
Assert.Equal(61, response.Usage.InputTokenCount);
Assert.Equal(16, response.Usage.OutputTokenCount);
Assert.Equal(77, response.Usage.TotalTokenCount);
+ Assert.Equal(13, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 13 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1351,12 +1351,12 @@ public async Task FunctionCallContent_Streaming()
Assert.Equal(61, usage.Details.InputTokenCount);
Assert.Equal(16, usage.Details.OutputTokenCount);
Assert.Equal(77, usage.Details.TotalTokenCount);
+ Assert.Equal(0, usage.Details.CachedInputTokenCount);
+ Assert.Equal(90, usage.Details.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 0 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1493,11 +1493,11 @@ public async Task AssistantMessageWithBothToolsAndContent_NonStreaming()
Assert.Equal(42, response.Usage.InputTokenCount);
Assert.Equal(15, response.Usage.OutputTokenCount);
Assert.Equal(57, response.Usage.TotalTokenCount);
+ Assert.Equal(20, response.Usage.CachedInputTokenCount);
+ Assert.Equal(90, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 20 },
- { "OutputTokenDetails.ReasoningTokenCount", 90 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
@@ -1608,11 +1608,11 @@ private static async Task DataContentMessage_Image_AdditionalPropertyDetail_NonS
Assert.Equal(8513, response.Usage.InputTokenCount);
Assert.Equal(56, response.Usage.OutputTokenCount);
Assert.Equal(8569, response.Usage.TotalTokenCount);
+ Assert.Equal(0, response.Usage.CachedInputTokenCount);
+ Assert.Equal(0, response.Usage.ReasoningTokenCount);
Assert.Equal(new Dictionary
{
{ "InputTokenDetails.AudioTokenCount", 0 },
- { "InputTokenDetails.CachedTokenCount", 0 },
- { "OutputTokenDetails.ReasoningTokenCount", 0 },
{ "OutputTokenDetails.AudioTokenCount", 0 },
{ "OutputTokenDetails.AcceptedPredictionTokenCount", 0 },
{ "OutputTokenDetails.RejectedPredictionTokenCount", 0 },
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
index 94d767f67d4..34526b683bd 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
@@ -4584,12 +4584,12 @@ public async Task ResponseWithUsageDetails_ParsesTokenCounts()
var response = await client.GetResponseAsync("test");
Assert.NotNull(response.Usage);
+ Assert.Null(response.Usage.AdditionalCounts);
Assert.Equal(50, response.Usage.InputTokenCount);
Assert.Equal(25, response.Usage.OutputTokenCount);
Assert.Equal(75, response.Usage.TotalTokenCount);
- Assert.NotNull(response.Usage.AdditionalCounts);
- Assert.Equal(10, response.Usage.AdditionalCounts["InputTokenDetails.CachedTokenCount"]);
- Assert.Equal(5, response.Usage.AdditionalCounts["OutputTokenDetails.ReasoningTokenCount"]);
+ Assert.Equal(10, response.Usage.CachedInputTokenCount);
+ Assert.Equal(5, response.Usage.ReasoningTokenCount);
}
[Fact]