diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 5dd1b3c0bc6..db9cbe01432 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -1404,7 +1404,11 @@ class LiteLLMAnthropicMessagesAdapter: @staticmethod def _positive_int(value: object) -> int: - if isinstance(value, (int, float)) and not isinstance(value, bool) and value > 0: + if isinstance(value, bool): + return 0 + if isinstance(value, int) and value > 0: + return value + if isinstance(value, float) and value.is_integer() and value > 0: return int(value) return 0 diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 9ae83eb831e..1d17cb93699 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -2146,6 +2146,67 @@ def test_translate_openai_response_to_anthropic_cache_tokens_from_prompt_tokens_ assert anthropic_response["usage"]["cache_read_input_tokens"] == 30 +def test_translate_openai_usage_to_anthropic_cache_tokens_from_dict_details_with_integral_floats(): + usage = Usage( + prompt_tokens=120, + completion_tokens=50, + total_tokens=170, + ) + usage.prompt_tokens_details = { + "cached_tokens": 30.0, + "cache_write_tokens": 20.0, + } + + anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta( + usage + ) + + assert anthropic_usage["input_tokens"] == 70 + assert anthropic_usage["output_tokens"] == 50 + assert anthropic_usage["cache_read_input_tokens"] == 30 + assert anthropic_usage["cache_creation_input_tokens"] == 20 + + +def test_translate_openai_usage_to_anthropic_ignores_fractional_cache_tokens(): + usage = Usage( + prompt_tokens=120, + completion_tokens=50, + total_tokens=170, + ) + usage.prompt_tokens_details = { + "cached_tokens": 30.5, + "cache_creation_tokens": 20.25, + } + + anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta( + usage + ) + + assert anthropic_usage["input_tokens"] == 120 + assert anthropic_usage["output_tokens"] == 50 + assert "cache_read_input_tokens" not in anthropic_usage + assert "cache_creation_input_tokens" not in anthropic_usage + + +def test_translate_openai_usage_to_anthropic_ignores_bool_cache_tokens(): + usage = Usage( + prompt_tokens=120, + completion_tokens=50, + total_tokens=170, + ) + usage.cache_read_input_tokens = True + usage.cache_creation_input_tokens = True + + anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta( + usage + ) + + assert anthropic_usage["input_tokens"] == 120 + assert anthropic_usage["output_tokens"] == 50 + assert "cache_read_input_tokens" not in anthropic_usage + assert "cache_creation_input_tokens" not in anthropic_usage + + def test_translate_openai_response_to_anthropic_cache_creation_from_prompt_tokens_details(): from litellm.types.utils import PromptTokensDetailsWrapper @@ -2288,6 +2349,41 @@ def test_translate_streaming_openai_response_to_anthropic_cache_tokens_from_prom assert message_delta["usage"]["cache_creation_input_tokens"] == 20 +def test_translate_streaming_openai_response_to_anthropic_cache_tokens_from_hidden_params_usage(): + from litellm.types.utils import PromptTokensDetailsWrapper + + usage = Usage( + prompt_tokens=120, + completion_tokens=50, + total_tokens=170, + prompt_tokens_details=PromptTokensDetailsWrapper( + cached_tokens=30, + cache_creation_tokens=20, + ), + ) + response = ModelResponseStream( + choices=[ + StreamingChoices( + index=0, + delta=Delta(), + finish_reason="stop", + ) + ], + ) + response._hidden_params = {"usage": usage} + + adapter = LiteLLMAnthropicMessagesAdapter() + message_delta = adapter.translate_streaming_openai_response_to_anthropic( + response=response, + current_content_block_index=0, + ) + + assert message_delta["usage"]["input_tokens"] == 70 + assert message_delta["usage"]["output_tokens"] == 50 + assert message_delta["usage"]["cache_read_input_tokens"] == 30 + assert message_delta["usage"]["cache_creation_input_tokens"] == 20 + + # ===================================================================== # Web Search Tool Transformation Tests # =====================================================================