Skip to content

Commit bf5246b

Browse files
committed
fix: Enhance ResponseAPI usage handling for None values and normalize token details
1 parent 4c9bd3e commit bf5246b

File tree

2 files changed

+44
-6
lines changed

2 files changed

+44
-6
lines changed

litellm/responses/utils.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from litellm._logging import verbose_logger
1818
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
1919
from litellm.types.llms.openai import (
20+
InputTokensDetails,
21+
OutputTokensDetails,
2022
ResponseAPIUsage,
2123
ResponsesAPIOptionalRequestParams,
2224
ResponsesAPIResponse,
@@ -370,9 +372,42 @@ def _transform_response_api_usage_to_chat_usage(
370372
completion_tokens=0,
371373
total_tokens=0,
372374
)
373-
response_api_usage: ResponseAPIUsage = (
374-
ResponseAPIUsage(**usage) if isinstance(usage, dict) else usage
375-
)
375+
if isinstance(usage, dict):
376+
usage_clean = usage.copy()
377+
# Ensure numeric fields default to zero rather than None
378+
for numeric_key in ("input_tokens", "output_tokens", "total_tokens"):
379+
if usage_clean.get(numeric_key) is None:
380+
usage_clean[numeric_key] = 0
381+
382+
# Drop detail fields when provider returns None, or clean nested None values
383+
for detail_key in ("input_tokens_details", "output_tokens_details"):
384+
detail_value = usage_clean.get(detail_key)
385+
if detail_value is None:
386+
usage_clean.pop(detail_key, None)
387+
elif isinstance(detail_value, dict):
388+
usage_clean[detail_key] = {
389+
k: v for k, v in detail_value.items() if v is not None
390+
}
391+
392+
response_api_usage: ResponseAPIUsage = ResponseAPIUsage(**usage_clean)
393+
else:
394+
response_api_usage = usage
395+
396+
# Normalise token detail fields so they match OpenAI format
397+
input_details = response_api_usage.input_tokens_details
398+
if input_details is None:
399+
input_details = InputTokensDetails(cached_tokens=0)
400+
elif input_details.cached_tokens is None:
401+
input_details.cached_tokens = 0
402+
response_api_usage.input_tokens_details = input_details
403+
404+
output_details = response_api_usage.output_tokens_details
405+
if output_details is None:
406+
output_details = OutputTokensDetails(reasoning_tokens=0)
407+
elif output_details.reasoning_tokens is None:
408+
output_details.reasoning_tokens = 0
409+
response_api_usage.output_tokens_details = output_details
410+
376411
prompt_tokens: int = response_api_usage.input_tokens or 0
377412
completion_tokens: int = response_api_usage.output_tokens or 0
378413
prompt_tokens_details: Optional[PromptTokensDetails] = None

tests/test_litellm/responses/test_responses_utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,11 @@ def test_transform_response_api_usage_with_none_values(self):
188188
"""Test transformation handles None values properly"""
189189
# Setup
190190
usage = {
191-
"input_tokens": 0, # Changed from None to 0
191+
"input_tokens": None,
192192
"output_tokens": 20,
193-
"total_tokens": 20,
194-
"output_tokens_details": {"reasoning_tokens": 5},
193+
"total_tokens": None,
194+
"input_tokens_details": None,
195+
"output_tokens_details": {"reasoning_tokens": None},
195196
}
196197

197198
# Execute
@@ -203,3 +204,5 @@ def test_transform_response_api_usage_with_none_values(self):
203204
assert result.prompt_tokens == 0
204205
assert result.completion_tokens == 20
205206
assert result.total_tokens == 20
207+
assert result.prompt_tokens_details is not None
208+
assert result.prompt_tokens_details.cached_tokens == 0

0 commit comments

Comments
 (0)