Fix Anthropic streaming reasoning token usage - #27319
Conversation
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
|
|
Greptile SummaryThis change corrects the Anthropic streaming path where emitted thinking-delta chunks were never stored, causing the final usage chunk to always report zero reasoning tokens. Thinking text is now accumulated in the iterator and forwarded to the existing usage calculator so the token split is populated.
Confidence Score: 4/5The change is safe to merge; it is narrowly scoped to the streaming iterator and adds no new code paths that could break existing non-reasoning-mode callers. The accumulation logic is correct and consistent with the non-streaming path. The one item worth watching is that litellm/llms/anthropic/chat/transformation.py — the
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/chat/handler.py | Adds reasoning_content_chunks accumulator to ModelResponseIterator and feeds the joined text into _handle_usage so the existing calculate_usage path can estimate reasoning_tokens for streaming responses; the token split uses a local token_counter approximation (pre-existing design) that can yield slightly inaccurate text_tokens. |
| tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py | Adds test_streaming_thinking_deltas_count_reasoning_tokens_in_usage — a new unit test (no real network calls) that feeds synthetic Anthropic stream events through chunk_parser and asserts that reasoning deltas are forwarded and final usage is split into reasoning_tokens > 0 and matching text_tokens. |
Comments Outside Diff (1)
-
litellm/llms/anthropic/chat/transformation.py, line 2234-2241 (link)The
reasoning_tokensvalue is derived from a localtoken_counterestimate on the accumulated thinking text, andtext_tokensis thencompletion_tokens - reasoning_tokens. Iftoken_counteroverestimates for a long reasoning chain,text_tokensgoes negative. Amax(0, ...)clamp keeps the value well-formed. The same pre-existing pattern on the non-streaming call site would benefit from the same guard.
Reviews (1): Last reviewed commit: "fix anthropic streaming reasoning token ..." | Re-trigger Greptile
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
* fix anthropic streaming reasoning token usage Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * test anthropic streaming reasoning usage end to end Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * address anthropic reasoning token text split Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> * harden anthropic reasoning usage for mocked tokens Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com> --------- Co-authored-by: oss-agent-shin <279349115+oss-agent-shin@users.noreply.github.com> Co-authored-by: ishaan-berri <ishaan-berri@users.noreply.github.com>
Summary
Anthropic streaming responses emitted thinking deltas but discarded the thinking text before final usage calculation. This stores streamed thinking text in the iterator and passes it to the existing Anthropic usage calculator so
completion_tokens_details.reasoning_tokensis populated andtext_tokensexcludes reasoning tokens. The usage split now also caps locally-estimated reasoning tokens to numeric provider-reported output totals sotext_tokenscannot go negative and mocked token values remain safe.Repro
Send an Anthropic chat completion stream with extended thinking enabled and
stream_options.include_usage=true. Reasoning deltas arrive during the stream, but the final usage chunk previously reportedreasoning_tokens: 0and counted all output tokens as text.Evidence
End-to-end local transcript using
litellm.completion()against an Anthropic-compatible HTTP server:The same flow is now pinned in
test_anthropic_completion_streaming_usage_matches_non_streaming_with_thinking.Tests
test_streaming_thinking_deltas_count_reasoning_tokens_in_usageintests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py.test_anthropic_completion_streaming_usage_matches_non_streaming_with_thinking, which runslitellm.completion()against a local Anthropic-compatible HTTP server and compares non-streaming vs streaming usage.test_calculate_usage_clamps_text_tokens_when_reasoning_estimate_exceeds_outputfor Greptile’s token split concern.test_calculate_usage_handles_mocked_output_tokens_with_reasoning_contentfor the mocked usage object edge case surfaced by CI.reasoning_tokens=0.uv run pytest tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py -q(248 passed).uv run black ., then kept only scoped formatting changes.uv lock --check,uv run --no-sync black --check --exclude '/enterprise/' .inlitellm/,uv run --no-sync ruff check .inlitellm/,uv run --no-sync mypy .inlitellm/, circular import check, and import safety check.CI
lint,integrations,proxy-infra, andtest.proxy_e2e_anthropic_messages_tests,build_and_test, andllm_translation_testing.Review
text_tokensand keeps the split well-formed.Relevant issues
Fixes the Anthropic streaming reasoning token accounting bug.
Linear ticket
N/A
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirementmake test-unitScreenshots / Proof of Fix
See
## Evidencefor request/response transcript.Type
🐛 Bug Fix
✅ Test
Changes
ModelResponseIterator.