diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 907a6d85a1c..34d259d2586 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -5,6 +5,7 @@ from collections.abc import Mapping from typing import TYPE_CHECKING, Any, Final, NoReturn, cast import httpx +from pydantic import ValidationError import litellm from litellm.constants import ( @@ -39,6 +40,7 @@ from litellm.types.llms.anthropic import ( AnthropicMessagesTool, AnthropicMessagesToolChoice, AnthropicOutputSchema, + AnthropicOutputTokensDetails, AnthropicSystemMessageContent, AnthropicThinkingParam, AnthropicWebSearchTool, @@ -2129,10 +2131,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): details: Final = usage_object.get("output_tokens_details") if not isinstance(details, Mapping): return None - thinking_tokens: Final = cast(Mapping[str, object], details).get("thinking_tokens") - if isinstance(thinking_tokens, bool) or not isinstance(thinking_tokens, (int, float)): + try: + parsed_details: Final = AnthropicOutputTokensDetails.model_validate(details) + except ValidationError: return None - return int(thinking_tokens) + return parsed_details.thinking_tokens def calculate_usage( self, diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index bb861030d86..ee0d2c098f3 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -609,6 +609,12 @@ class AnthropicResponseUsageBlock(BaseModel): output_tokens: int +class AnthropicOutputTokensDetails(BaseModel): + model_config = ConfigDict(extra="allow", strict=True) + + thinking_tokens: int | None = None + + AnthropicFinishReason = Literal["end_turn", "max_tokens", "stop_sequence", "tool_use"] diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 6fc61da326a..2c59ee59df8 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -2477,7 +2477,7 @@ def test_calculate_usage_reported_thinking_tokens_override_text_estimate(): @pytest.mark.parametrize( "output_tokens_details", - [None, {}, {"thinking_tokens": None}], + [None, {}, {"thinking_tokens": None}, {"thinking_tokens": "180"}, {"thinking_tokens": True}], ) def test_calculate_usage_falls_back_to_estimate_without_reported_thinking_tokens(output_tokens_details): """Older responses have no thinking_tokens, so the visible-text estimate must still apply."""