Account for Anthropic thinking tokens in usage details - #554
Account for Anthropic thinking tokens in usage details#554PratikDhanave (PratikDhanave) wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
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.ThinkingTokensintoUsageDetails.ReasoningTokenCount. - Anthropic streaming: preserve
OutputTokensDetailswhen reconstructinganthropic.UsagefromMessageDeltaUsage. - Copilot: surface reasoning tokens from
assistant.usageevents 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.
fb35d43 to
1753be4
Compare
This comment has been minimized.
This comment has been minimized.
1753be4 to
0e1eb69
Compare
This comment has been minimized.
This comment has been minimized.
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.
Covers the toUsageDetailsDelta path: thinking tokens arriving on the streamed message_delta usage must surface as ReasoningTokenCount.
0e1eb69 to
51f6d56
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
# Conflicts: # provider/anthropicprovider/agent_test.go
Head branch was pushed to by a user without write access
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
API Consistency Review — PR #554Scope: Public API change: None. Cross-repo parity
ConclusionThis is a targeted bug fix that brings the Anthropic provider into alignment with the Go framework's own usage-reporting convention (the same ✅ Parity approved. No cross-repo consistency issues found.
|
The Anthropic SDK exposes
Usage.OutputTokensDetails.ThinkingTokens— "the number of output tokens the model generated as internal reasoning."toUsageDetailsmapped input, output, total, and both cache fields but never read it:For any extended-thinking request (this provider explicitly supports it — it emits
ThinkingBlock/ThinkingDeltaasTextReasoningContent), those tokens were silently dropped: the framework’s dedicatedUsageDetails.ReasoningTokenCount(populated by the OpenAI, Gemini, and Copilot providers, and surfaced as an OTel telemetry attribute) stayed0.toUsageDetailsDeltaadditionally rebuilt theUsagefrom only four scalar fields, omittingOutputTokensDetails, so the streaming path could never recover it.Fix
toUsageDetails:ReasoningTokenCount: usage.OutputTokensDetails.ThinkingTokens.toUsageDetailsDelta: carryOutputTokensDetailsthrough when reconstructing theUsage.Thinking tokens are a subset of output tokens, so
TotalTokenCountis unchanged — only the missing breakdown field is added, matching the OpenAI providers.Test
TestUsageReasoningTokensreturns a non-streaming response withoutput_tokens_details.thinking_tokens: 35and assertsReasoningTokenCount == 35. It fails on the old code (0) and passes with the fix.