diff --git a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py index aa4e0cf5495..f6cfc81c18b 100644 --- a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py @@ -1086,7 +1086,7 @@ class ChunkProcessor: except Exception: # don't allow this failing to block a complete streaming response from being returned print_verbose("token_counter failed, assuming prompt tokens is 0") returned_usage.prompt_tokens = 0 - returned_usage.completion_tokens = ( + resolved_completion_tokens: Final = ( completion_tokens if completion_tokens is not None else ( @@ -1097,7 +1097,9 @@ class ChunkProcessor: ) + (reasoning_tokens or 0) ) + + (reasoning_tokens or 0) ) + returned_usage.completion_tokens = resolved_completion_tokens returned_usage.total_tokens = returned_usage.prompt_tokens + returned_usage.completion_tokens if cache_creation_input_tokens is not None: diff --git a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py index af763da2d87..744c6c29711 100644 --- a/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py @@ -1493,6 +1493,43 @@ def test_count_reasoning_tokens_counts_visible_reasoning(): assert processor.count_reasoning_tokens(response) > 0 +def test_stream_chunk_builder_includes_reasoning_in_estimated_completion_tokens(): + reasoning_content = "let me count the primes under thirty" + visible_content = "10" + chunks = [ + ModelResponseStream( + id="chatcmpl-estimated-reasoning", + model="claude-opus-4-8", + choices=[ + StreamingChoices( + finish_reason=None, + index=0, + delta=Delta(role="assistant", reasoning_content=reasoning_content), + ) + ], + ), + ModelResponseStream( + id="chatcmpl-estimated-reasoning", + model="claude-opus-4-8", + choices=[ + StreamingChoices( + finish_reason="stop", + index=0, + delta=Delta(content=visible_content), + ) + ], + ), + ] + + response = stream_chunk_builder(chunks=chunks, messages=[{"role": "user", "content": "count"}]) + + assert response is not None + reasoning_tokens = response.usage.completion_tokens_details.reasoning_tokens + assert reasoning_tokens is not None + assert response.usage.completion_tokens > reasoning_tokens + assert response.usage.total_tokens == response.usage.prompt_tokens + response.usage.completion_tokens + + @pytest.mark.parametrize( "estimated_reasoning_tokens, expected_reasoning_tokens, expected_text_tokens", [(40, 40, 60), (250, 100, 0)],