Skip to content

Account for Anthropic thinking tokens in usage details#554

Open
PratikDhanave wants to merge 4 commits into
microsoft:mainfrom
PratikDhanave:fix-anthropic-reasoning-token-count
Open

Account for Anthropic thinking tokens in usage details#554
PratikDhanave wants to merge 4 commits into
microsoft:mainfrom
PratikDhanave:fix-anthropic-reasoning-token-count

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

The Anthropic SDK exposes Usage.OutputTokensDetails.ThinkingTokens"the number of output tokens the model generated as internal reasoning." toUsageDetails mapped input, output, total, and both cache fields but never read it:

details := message.UsageDetails{
    InputTokenCount:       usage.InputTokens,
    OutputTokenCount:      usage.OutputTokens,
    TotalTokenCount:       usage.InputTokens + usage.OutputTokens,
    CachedInputTokenCount: usage.CacheReadInputTokens,
}

For any extended-thinking request (this provider explicitly supports it — it emits ThinkingBlock/ThinkingDelta as TextReasoningContent), those tokens were silently dropped: the framework’s dedicated UsageDetails.ReasoningTokenCount (populated by the OpenAI, Gemini, and Copilot providers, and surfaced as an OTel telemetry attribute) stayed 0. toUsageDetailsDelta additionally rebuilt the Usage from only four scalar fields, omitting OutputTokensDetails, so the streaming path could never recover it.

Fix

  • toUsageDetails: ReasoningTokenCount: usage.OutputTokensDetails.ThinkingTokens.
  • toUsageDetailsDelta: carry OutputTokensDetails through when reconstructing the Usage.

Thinking tokens are a subset of output tokens, so TotalTokenCount is unchanged — only the missing breakdown field is added, matching the OpenAI providers.

Test

TestUsageReasoningTokens returns a non-streaming response with output_tokens_details.thinking_tokens: 35 and asserts ReasoningTokenCount == 35. It fails on the old code (0) and passes with the fix.

assistantUsageUpdate mapped input, output, total, and cache-read tokens
but dropped AssistantUsageData.ReasoningTokens, so the framework's
dedicated ReasoningTokenCount field stayed zero for reasoning models.
ReasoningTokens is a subset of the output tokens, so TotalTokenCount is
unaffected; only the breakdown field was missing.

Map ReasoningTokens to UsageDetails.ReasoningTokenCount, matching the
OpenAI and Gemini providers.
toUsageDetails mapped input, output, total, and cache tokens but dropped
Usage.OutputTokensDetails.ThinkingTokens (the tokens the model generated
as internal reasoning), so the framework's dedicated ReasoningTokenCount
field stayed zero for extended-thinking requests. toUsageDetailsDelta
also reconstructed the Usage without OutputTokensDetails, so the
streaming path could not recover it either.

Map ThinkingTokens to UsageDetails.ReasoningTokenCount and carry
OutputTokensDetails through the delta path, matching the OpenAI, Gemini,
and Copilot providers. Thinking tokens are a subset of output tokens, so
TotalTokenCount is unchanged.
Copilot AI review requested due to automatic review settings July 19, 2026 11:13
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 19, 2026 11:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves token-usage accounting by ensuring “reasoning/thinking” tokens emitted by providers are surfaced via UsageDetails.ReasoningTokenCount, including in Anthropic streaming deltas, so telemetry/OTel breakdowns are accurate.

Changes:

  • Anthropic: map Usage.OutputTokensDetails.ThinkingTokens into UsageDetails.ReasoningTokenCount.
  • Anthropic streaming: preserve OutputTokensDetails when reconstructing anthropic.Usage from MessageDeltaUsage.
  • Copilot: surface reasoning tokens from assistant.usage events and add a regression test; add an Anthropic non-streaming regression test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
provider/copilotprovider/copilot.go Adds ReasoningTokenCount mapping in Copilot usage updates.
provider/copilotprovider/copilot_test.go Adds coverage asserting Copilot usage events expose reasoning tokens.
provider/anthropicprovider/agent.go Maps Anthropic thinking tokens into ReasoningTokenCount and carries details through streaming delta reconstruction.
provider/anthropicprovider/agent_test.go Adds a non-streaming Anthropic regression test for thinking tokens.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 210 to 214
OutputTokens: usage.OutputTokens,
CacheCreationInputTokens: usage.CacheCreationInputTokens,
CacheReadInputTokens: usage.CacheReadInputTokens,
OutputTokensDetails: usage.OutputTokensDetails,
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TestUsageReasoningTokens_Streaming, which covers the toUsageDetailsDelta path (thinking tokens on the streamed message_deltaReasoningTokenCount); red on the old code, green with the fix.

Covers the toUsageDetailsDelta path: thinking tokens arriving on the
streamed message_delta usage must surface as ReasoningTokenCount.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants