diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py index c532d8ea27c..2abcc679eef 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py @@ -11,6 +11,7 @@ from typing import Any, List, Optional import httpx +from litellm.types.utils import Usage from litellm.llms.bedrock.chat.invoke_transformations.amazon_qwen3_transformation import ( AmazonQwen3Config, ) @@ -79,10 +80,11 @@ class AmazonQwen2Config(AmazonQwen3Config): # Set usage information if available in response if "usage" in response_data: usage_data = response_data["usage"] - if hasattr(model_response, 'usage'): - model_response.usage.prompt_tokens = usage_data.get("prompt_tokens", 0) - model_response.usage.completion_tokens = usage_data.get("completion_tokens", 0) - model_response.usage.total_tokens = usage_data.get("total_tokens", 0) + model_response.usage = Usage( + prompt_tokens=usage_data.get("prompt_tokens", 0), + completion_tokens=usage_data.get("completion_tokens", 0), + total_tokens=usage_data.get("total_tokens", 0), + ) return model_response diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py index b3a957ce0f8..12333623f51 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py @@ -10,6 +10,7 @@ from typing import Any, List, Optional import httpx +from litellm.types.utils import Usage from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation import ( AmazonInvokeConfig, @@ -201,10 +202,11 @@ class AmazonQwen3Config(AmazonInvokeConfig, BaseConfig): # Set usage information if available in response if "usage" in response_data: usage_data = response_data["usage"] - if hasattr(model_response, 'usage'): - model_response.usage.prompt_tokens = usage_data.get("prompt_tokens", 0) - model_response.usage.completion_tokens = usage_data.get("completion_tokens", 0) - model_response.usage.total_tokens = usage_data.get("total_tokens", 0) + model_response.usage = Usage( + prompt_tokens=usage_data.get("prompt_tokens", 0), + completion_tokens=usage_data.get("completion_tokens", 0), + total_tokens=usage_data.get("total_tokens", 0), + ) return model_response diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 94c387dd22c..e8d6ac79708 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -1826,7 +1826,7 @@ class ModelResponse(ModelResponseBase): else: usage = usage elif stream is None or stream is False: - usage = Usage() + usage = None # avoid constructing throwaway Usage; set by convert_to_model_response_object if hidden_params: self._hidden_params = hidden_params diff --git a/tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py b/tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py index 15d625b681f..a722aec7eb4 100644 --- a/tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py +++ b/tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py @@ -1453,3 +1453,47 @@ def test_convert_to_model_response_object_falsy_id_preserves_auto_generated(fals ) assert result.id == original_id assert result.id.startswith("chatcmpl-") + + +def test_convert_to_model_response_object_default_usage_overwritten(): + """ + Regression test: convert_to_model_response_object must properly set Usage + on a ModelResponse that only has the default Usage from ModelResponse.__init__() + (i.e. no extra litellm.Usage() set via setattr beforehand). + + This validates the optimization of removing the redundant + `setattr(model_response, "usage", litellm.Usage())` in completion(). + """ + mr = ModelResponse() + # usage is not set by default (optimization: avoid constructing throwaway Usage) + assert not hasattr(mr, "usage") + + response_object = { + "id": "chatcmpl-usage-test", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello"}, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 15, + "completion_tokens": 7, + "total_tokens": 22, + }, + "model": "gpt-4o", + } + + result = convert_to_model_response_object( + model_response_object=mr, + response_object=response_object, + stream=False, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + assert isinstance(result, ModelResponse) + assert result.usage.prompt_tokens == 15 + assert result.usage.completion_tokens == 7 + assert result.usage.total_tokens == 22