[LLM Translation] Fix Model Usage not having text tokens (#13234)

* fix + test

* remove test comments

* fix mypy

* fix mypy

* fix tests
This commit is contained in:
Jugal D. Bhatt
2025-08-04 21:06:49 +05:30
committed by GitHub
parent 98102e2804
commit 36229dc69f
5 changed files with 68 additions and 7 deletions
+2 -2
View File
@@ -1265,7 +1265,7 @@ class BaseTokenUsageProcessor:
Combine multiple Usage objects into a single Usage object, checking model keys for nested values.
"""
from litellm.types.utils import (
CompletionTokensDetails,
CompletionTokensDetailsWrapper,
PromptTokensDetailsWrapper,
Usage,
)
@@ -1320,7 +1320,7 @@ class BaseTokenUsageProcessor:
not hasattr(combined, "completion_tokens_details")
or not combined.completion_tokens_details
):
combined.completion_tokens_details = CompletionTokensDetails()
combined.completion_tokens_details = CompletionTokensDetailsWrapper()
# Check what keys exist in the model's completion_tokens_details
for attr in usage.completion_tokens_details.model_fields:
@@ -527,7 +527,12 @@ class ChunkProcessor:
returned_usage, "cache_read_input_tokens", cache_read_input_tokens
) # for anthropic
if completion_tokens_details is not None:
returned_usage.completion_tokens_details = completion_tokens_details
if isinstance(completion_tokens_details, CompletionTokensDetails):
returned_usage.completion_tokens_details = CompletionTokensDetailsWrapper(
**completion_tokens_details.model_dump()
)
else:
returned_usage.completion_tokens_details = completion_tokens_details
if reasoning_tokens is not None:
if returned_usage.completion_tokens_details is None:
+3
View File
@@ -888,6 +888,9 @@ class Usage(CompletionUsage):
server_tool_use: Optional[ServerToolUse] = None
completion_tokens_details: Optional[CompletionTokensDetailsWrapper] = None
"""Breakdown of tokens used in a completion."""
prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None
"""Breakdown of tokens used in the prompt."""
+4 -4
View File
@@ -2155,8 +2155,8 @@ async def test_caching_kwargs_input(sync_mode):
Message,
ModelResponse,
Usage,
CompletionTokensDetails,
PromptTokensDetails,
CompletionTokensDetailsWrapper,
PromptTokensDetailsWrapper,
)
from datetime import datetime
@@ -2187,10 +2187,10 @@ async def test_caching_kwargs_input(sync_mode):
completion_tokens=31,
prompt_tokens=16,
total_tokens=47,
completion_tokens_details=CompletionTokensDetails(
completion_tokens_details=CompletionTokensDetailsWrapper(
audio_tokens=None, reasoning_tokens=0
),
prompt_tokens_details=PromptTokensDetails(
prompt_tokens_details=PromptTokensDetailsWrapper(
audio_tokens=None, cached_tokens=0
),
),
@@ -73,3 +73,56 @@ def test_usage_dump():
new_usage = Usage(**current_usage.model_dump())
assert new_usage.prompt_tokens_details.web_search_requests == 1
def test_usage_completion_tokens_details_text_tokens():
from litellm.types.utils import Usage
# Test data from the reported issue
usage_data = {
'completion_tokens': 77,
'prompt_tokens': 11937,
'total_tokens': 12014,
'completion_tokens_details': {
'accepted_prediction_tokens': None,
'audio_tokens': None,
'reasoning_tokens': 65,
'rejected_prediction_tokens': None,
'text_tokens': 12
},
'prompt_tokens_details': {
'audio_tokens': None,
'cached_tokens': None,
'text_tokens': 11937,
'image_tokens': None
}
}
# Create Usage object
u = Usage(**usage_data)
# Verify the object has the text_tokens field
assert hasattr(u.completion_tokens_details, 'text_tokens')
assert u.completion_tokens_details.text_tokens == 12
# Get model_dump output
dump_result = u.model_dump()
# Verify text_tokens is present in the model_dump output
assert 'completion_tokens_details' in dump_result
assert 'text_tokens' in dump_result['completion_tokens_details']
assert dump_result['completion_tokens_details']['text_tokens'] == 12
# Verify the full completion_tokens_details structure
expected_completion_details = {
'accepted_prediction_tokens': None,
'audio_tokens': None,
'reasoning_tokens': 65,
'rejected_prediction_tokens': None,
'text_tokens': 12
}
assert dump_result['completion_tokens_details'] == expected_completion_details
# Verify round-trip serialization works
new_usage = Usage(**dump_result)
assert new_usage.completion_tokens_details.text_tokens == 12