Python: fix Anthropic streaming double-counting token usage - #7162
Merged
Evan Mattson (moonbox3) merged 3 commits intoJul 30, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes incorrect token accounting for Anthropic streaming responses in the Python Anthropic package by converting Anthropic’s cumulative usage snapshots into incremental usage updates that can be safely summed by ChatResponse.from_updates, aligning streaming totals with the non-streaming path.
Changes:
- Thread a per-stream
UsageDetailsaccumulator through the streaming generator to emit usage increments instead of cumulative snapshots. - Update
_process_stream_eventto optionally accept the per-stream accumulator and apply_incremental_usageto bothmessage_startandmessage_deltausage. - Add regression tests covering both the basic cumulative-output case and the server-tool cumulative-input case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| python/packages/anthropic/agent_framework_anthropic/_chat_client.py | Adds per-stream usage reconciliation (_incremental_usage) and threads an accumulator through streaming event processing to prevent double-counting. |
| python/packages/anthropic/tests/test_anthropic_client.py | Adds regression tests ensuring streaming usage totals match Anthropic-reported cumulative totals for both output-only and input+output delta usage. |
Eduard van Valkenburg (eavanvalkenburg)
left a comment
Member
There was a problem hiding this comment.
overall this is fine, but it is redoing stuff already covered in the core library
Contributor
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
Contributor
|
Hey Yufeng He (@he-yufeng) can you please address the comments on the PR if you're still working on it? |
- accumulate the emitted totals in a plain dict instead of string-cast TypedDict views, so static checkers see real types throughout - compute the increment through _types.add_usage_details with negated emitted totals instead of a hand-rolled subtraction loop; keys absent from a snapshot stay untouched, matching the partial-delta semantics
Yufeng He (he-yufeng)
had a problem deploying
to
github-app-auth
July 28, 2026 06:45 — with
GitHub Actions
Failure
Contributor
Author
|
Both addressed in e198d31:
|
Yufeng He (he-yufeng)
temporarily deployed
to
github-app-auth
July 29, 2026 09:29 — with
GitHub Actions
Inactive
Eduard van Valkenburg (eavanvalkenburg)
approved these changes
Jul 30, 2026
Evan Mattson (moonbox3)
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
On the Anthropic streaming path, usage tokens are double-counted.
_process_stream_eventemits a usageContenton bothmessage_startandmessage_delta, andChatResponse.from_updatessums every usageContentit sees. But Anthropic'smessage_deltausage is a cumulative total for the message, not a per-delta increment (from their streaming docs: "The token counts shown in the usage field of the message_delta event are cumulative"). Summing themessage_startseed onto the cumulativemessage_deltatotal inflatesusage_details:output_token_countlands at 26 when the API reported 25 (the seed isoutput_tokens: 1), and for server-tool turns that also report cumulative input onmessage_delta, the prompt tokens get double-counted too.The streaming total should match what the non-streaming path produces.
Description & Review Guide
from_updatesexpects each update's usage to be an increment it can sum._process_stream_eventnow takes a per-streamemitted_usageaccumulator and emits the increment of each snapshot over what has already been emitted, so the summation reconstructs the final cumulative usage instead of inflating it. The accumulator lives in the per-request_stream()generator rather than on the client, so concurrent streams don't interfere, and when no accumulator is passed (a direct single-event call) the snapshot is returned unchanged, so existing callers and tests are untouched.response.usage_detailsafter a streaming Anthropic call now equals the cumulative totals the API reported, matching the non-streaming path. This fixes both the unconditional output-seed inflation and the server-tool input double-count._incremental_usage, and thatmessage_start's input-side counts are preserved whenmessage_deltacarries onlyoutput_tokens(the basic case).Two regression tests cover the basic case (output 25 / input 10, not 26) and the server-tool case where
message_deltaalso reports cumulative input (output 25 / input 12, not doubled).Related Issue
Fixes #7143
Contribution Checklist