diff --git a/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs b/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs index fc903291..8ea1ad2e 100644 --- a/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs +++ b/src/Anthropic.Tests/AnthropicClientExtensionsTestsBase.cs @@ -1290,8 +1290,9 @@ public async Task GetResponseAsync_WithCacheCreationTokens() Assert.NotNull(response); Assert.NotNull(response.Usage); - Assert.Equal(100, response.Usage.InputTokenCount); + Assert.Equal(175, response.Usage.InputTokenCount); Assert.Equal(10, response.Usage.OutputTokenCount); + Assert.Equal(185, response.Usage.TotalTokenCount); Assert.NotNull(response.Usage.AdditionalCounts); Assert.Equal(50L, response.Usage.AdditionalCounts["CacheCreationInputTokens"]); Assert.Equal(25L, response.Usage.AdditionalCounts["CacheReadInputTokens"]); @@ -2490,8 +2491,9 @@ public async Task GetResponseAsync_WithAdditionalUsageCounts() Assert.NotNull(response); Assert.NotNull(response.Usage); - Assert.Equal(100, response.Usage.InputTokenCount); + Assert.Equal(175, response.Usage.InputTokenCount); Assert.Equal(20, response.Usage.OutputTokenCount); + Assert.Equal(195, response.Usage.OutputTokenCount); Assert.NotNull(response.Usage.AdditionalCounts); Assert.Equal(50L, response.Usage.AdditionalCounts["CacheCreationInputTokens"]); Assert.Equal(25L, response.Usage.AdditionalCounts["CacheReadInputTokens"]); diff --git a/src/Anthropic/AnthropicClientExtensions.cs b/src/Anthropic/AnthropicClientExtensions.cs index a7fb6228..276dd5ae 100644 --- a/src/Anthropic/AnthropicClientExtensions.cs +++ b/src/Anthropic/AnthropicClientExtensions.cs @@ -905,7 +905,8 @@ private static UsageDetails ToUsageDetails(Usage usage) => usage.InputTokens, usage.OutputTokens, usage.CacheCreationInputTokens, - usage.CacheReadInputTokens + usage.CacheReadInputTokens, + usage.ServerToolUse ); private static UsageDetails ToUsageDetails(MessageDeltaUsage usage) => @@ -913,39 +914,58 @@ private static UsageDetails ToUsageDetails(MessageDeltaUsage usage) => usage.InputTokens, usage.OutputTokens, usage.CacheCreationInputTokens, - usage.CacheReadInputTokens + usage.CacheReadInputTokens, + usage.ServerToolUse ); private static UsageDetails ToUsageDetails( long? inputTokens, long? outputTokens, long? cacheCreationInputTokens, - long? cacheReadInputTokens + long? cacheReadInputTokens, + ServerToolUsage? serverToolUsage ) { UsageDetails usageDetails = new() { - InputTokenCount = inputTokens, + // From https://platform.claude.com/docs/en/build-with-claude/prompt-caching: + // "To calculate total input tokens:" + // "total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens" + InputTokenCount = NullableSum( + NullableSum(cacheReadInputTokens, cacheCreationInputTokens), + inputTokens + ), + OutputTokenCount = outputTokens, - TotalTokenCount = - (inputTokens is not null || outputTokens is not null) - ? (inputTokens ?? 0) + (outputTokens ?? 0) - : null, }; - if (cacheCreationInputTokens is not null) + usageDetails.TotalTokenCount = NullableSum( + usageDetails.InputTokenCount, + usageDetails.OutputTokenCount + ); + + if (cacheCreationInputTokens is > 0) { (usageDetails.AdditionalCounts ??= [])[nameof(Usage.CacheCreationInputTokens)] = cacheCreationInputTokens.Value; } - if (cacheReadInputTokens is not null) + if (cacheReadInputTokens is > 0) { (usageDetails.AdditionalCounts ??= [])[nameof(Usage.CacheReadInputTokens)] = cacheReadInputTokens.Value; } + if (serverToolUsage?.WebSearchRequests is > 0) + { + (usageDetails.AdditionalCounts ??= [])[nameof(ServerToolUsage.WebSearchRequests)] = + serverToolUsage.WebSearchRequests; + } + return usageDetails; + + static long? NullableSum(long? a, long? b) => + a is not null || b is not null ? (a ?? 0) + (b ?? 0) : null; } private static ChatFinishReason? ToFinishReason(ApiEnum? stopReason) => diff --git a/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs b/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs index e0579b90..2cee7389 100644 --- a/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs +++ b/src/Anthropic/Services/Beta/Messages/AnthropicBetaClientExtensions.cs @@ -1153,7 +1153,8 @@ private static UsageDetails ToUsageDetails(BetaUsage usage) => usage.InputTokens, usage.OutputTokens, usage.CacheCreationInputTokens, - usage.CacheReadInputTokens + usage.CacheReadInputTokens, + usage.ServerToolUse ); private static UsageDetails ToUsageDetails(BetaMessageDeltaUsage usage) => @@ -1161,39 +1162,66 @@ private static UsageDetails ToUsageDetails(BetaMessageDeltaUsage usage) => usage.InputTokens, usage.OutputTokens, usage.CacheCreationInputTokens, - usage.CacheReadInputTokens + usage.CacheReadInputTokens, + usage.ServerToolUse ); private static UsageDetails ToUsageDetails( long? inputTokens, long? outputTokens, long? cacheCreationInputTokens, - long? cacheReadInputTokens + long? cacheReadInputTokens, + BetaServerToolUsage? serverToolUsage ) { UsageDetails usageDetails = new() { - InputTokenCount = inputTokens, + // From https://platform.claude.com/docs/en/build-with-claude/prompt-caching: + // "To calculate total input tokens:" + // "total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens" + InputTokenCount = NullableSum( + NullableSum(cacheReadInputTokens, cacheCreationInputTokens), + inputTokens + ), + OutputTokenCount = outputTokens, - TotalTokenCount = - (inputTokens is not null || outputTokens is not null) - ? (inputTokens ?? 0) + (outputTokens ?? 0) - : null, }; - if (cacheCreationInputTokens is not null) + usageDetails.TotalTokenCount = NullableSum( + usageDetails.InputTokenCount, + usageDetails.OutputTokenCount + ); + + if (cacheCreationInputTokens is > 0) { (usageDetails.AdditionalCounts ??= [])[nameof(BetaUsage.CacheCreationInputTokens)] = cacheCreationInputTokens.Value; } - if (cacheReadInputTokens is not null) + if (cacheReadInputTokens is > 0) { (usageDetails.AdditionalCounts ??= [])[nameof(BetaUsage.CacheReadInputTokens)] = cacheReadInputTokens.Value; } + if (serverToolUsage?.WebFetchRequests is > 0) + { + (usageDetails.AdditionalCounts ??= [])[ + nameof(BetaServerToolUsage.WebFetchRequests) + ] = serverToolUsage.WebFetchRequests; + } + + if (serverToolUsage?.WebSearchRequests is > 0) + { + (usageDetails.AdditionalCounts ??= [])[ + nameof(BetaServerToolUsage.WebSearchRequests) + ] = serverToolUsage.WebSearchRequests; + } + return usageDetails; + + static long? NullableSum(long? a, long? b) => + a is not null || b is not null ? (a ?? 0) + (b ?? 0) : null; } private static ChatFinishReason? ToFinishReason(