Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2876,6 +2876,8 @@ def _transform_chat_completion_usage_to_responses_usage(
cached_tokens=prompt_details.cached_tokens if prompt_details.cached_tokens is not None else 0,
text_tokens=prompt_details.text_tokens,
audio_tokens=prompt_details.audio_tokens,
image_tokens=prompt_details.image_tokens,
video_tokens=prompt_details.video_tokens,
cached_tokens_details=(
cached_tokens_details if isinstance(cached_tokens_details, CachedTokensDetails) else None
),
Expand Down
1 change: 1 addition & 0 deletions litellm/responses/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ def _transform_response_api_usage_to_chat_usage(
cached_tokens_details=getattr(
response_api_usage.input_tokens_details, "cached_tokens_details", None
),
video_tokens=getattr(response_api_usage.input_tokens_details, "video_tokens", None),
cache_write_tokens=getattr(response_api_usage.input_tokens_details, "cache_write_tokens", None),
web_search_requests=getattr(response_api_usage.input_tokens_details, "web_search_requests", None),
google_maps_grounding_requests=getattr(
Expand Down
2 changes: 2 additions & 0 deletions litellm/types/llms/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,9 @@ class InputTokensDetails(BaseLiteLLMOpenAIResponseObject):
audio_tokens: int | None = None
cached_tokens: int = 0
cached_tokens_details: CachedTokensDetails | None = None
image_tokens: int | None = None
text_tokens: int | None = None
video_tokens: int | None = None

model_config = {"extra": "allow"}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2917,6 +2917,39 @@ def test_transform_usage_preserves_cache_write_tokens(self):
assert getattr(response_usage.input_tokens_details, "cache_write_tokens", None) == 800
assert response_usage.input_tokens_details.model_dump()["cache_write_tokens"] == 800

def test_transform_usage_preserves_input_modality_tokens(self):
"""Regression: the bridge dropped image and video input tokens.

Vertex reports prompt tokens split by modality, so a Live session that sends
camera frames arrives with image_tokens set. InputTokensDetails declared only
audio/cached/text, so those tokens were folded into text and lost their
attribution, and any per-modality rate could never apply to them.
"""
usage = Usage(
prompt_tokens=300,
completion_tokens=10,
total_tokens=310,
prompt_tokens_details=PromptTokensDetailsWrapper(
text_tokens=20, audio_tokens=80, image_tokens=150, video_tokens=50, cached_tokens=0
),
completion_tokens_details=CompletionTokensDetailsWrapper(text_tokens=10),
)

response_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage(
chat_completion_response=usage
)
details = response_usage.input_tokens_details
assert details is not None
assert getattr(details, "image_tokens", None) == 150
assert getattr(details, "video_tokens", None) == 50
assert getattr(details, "audio_tokens", None) == 80

from litellm.responses.utils import ResponseAPILoggingUtils

back = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(response_usage.model_dump())
assert back.prompt_tokens_details.image_tokens == 150
assert back.prompt_tokens_details.video_tokens == 50

def test_transform_usage_with_reasoning_tokens_gemini(self):
"""Test that reasoning_tokens from Gemini are properly transformed to output_tokens_details"""
# Setup: Simulate Gemini usage with thoughtsTokenCount
Expand Down
Loading