From 7adf073fb756d3f65db969c6390960cf513fe1bd Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 23:02:47 +0000 Subject: [PATCH] fix(anthropic-messages): build thinking usage without mutation and expect it in bridged integration cases Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../messages/adapters/transformation.py | 13 ++++++++---- .../streaming/test_stream_contracts.py | 6 +++++- .../translation/messages/bases/azure.py | 8 +++---- .../bases/bedrock_chat_completions.py | 2 +- .../messages/bases/bedrock_converse.py | 21 ++++++++++++++++--- .../messages/bases/bedrock_mantle.py | 8 +++---- .../translation/messages/bases/gemini.py | 5 ++++- .../translation/messages/bases/openai.py | 8 +++---- .../messages/bases/openai_responses.py | 2 +- .../translation/messages/bases/vertex_ai.py | 8 +++---- .../test_messages_reasoning_gemini.py | 3 ++- ...es_tool_search_bedrock_chat_completions.py | 2 +- ...hropic_messages_adapters_transformation.py | 8 +++---- 13 files changed, 61 insertions(+), 33 deletions(-) diff --git a/litellm/llms/anthropic/messages/adapters/transformation.py b/litellm/llms/anthropic/messages/adapters/transformation.py index 0818aaf0032..6d42157a32d 100644 --- a/litellm/llms/anthropic/messages/adapters/transformation.py +++ b/litellm/llms/anthropic/messages/adapters/transformation.py @@ -180,6 +180,7 @@ from litellm.types.llms.anthropic import ( ServerToolUsage, StreamingContentBlockDeltaType, UsageDelta, + UsageDeltaOutputTokensDetails, UsageIteration, ) from litellm.types.llms.anthropic_messages.anthropic_response import ( @@ -1608,14 +1609,18 @@ class LiteLLMAnthropicMessagesAdapter: if cache_read_input_tokens > 0: usage_delta["cache_read_input_tokens"] = cache_read_input_tokens reasoning_tokens: Final = cls._get_reasoning_tokens(usage) - if reasoning_tokens > 0: - usage_delta["output_tokens_details"] = {"thinking_tokens": reasoning_tokens} + usage_with_thinking: Final = ( + usage_delta + | UsageDelta(output_tokens_details=UsageDeltaOutputTokensDetails(thinking_tokens=reasoning_tokens)) + if reasoning_tokens > 0 + else usage_delta + ) if web_search_requests > 0: return UsageDelta( - **usage_delta, + **usage_with_thinking, server_tool_use=ServerToolUsage(web_search_requests=web_search_requests), ) - return usage_delta + return usage_with_thinking _translate_openai_usage_to_anthropic_usage_delta = translate_openai_usage_to_anthropic_usage_delta diff --git a/tests/integration/streaming/test_stream_contracts.py b/tests/integration/streaming/test_stream_contracts.py index 1d2a8b25792..488aa53f345 100644 --- a/tests/integration/streaming/test_stream_contracts.py +++ b/tests/integration/streaming/test_stream_contracts.py @@ -465,7 +465,11 @@ def test_messages_stream_opens_thinking_block_at_index_zero_for_reasoning_conten "message_stop", ), text message_delta: Final = next(event for event in events if event["type"] == "message_delta") - assert message_delta["usage"] == {"input_tokens": 11, "output_tokens": 6}, text + assert message_delta["usage"] == { + "input_tokens": 11, + "output_tokens": 6, + "output_tokens_details": {"thinking_tokens": 4}, + }, text requests: Final = wire.drain() assert len(requests) == 1 outbound: Final = json.loads(requests[0].body) diff --git a/tests/integration/translation/messages/bases/azure.py b/tests/integration/translation/messages/bases/azure.py index b731f7b7760..43e86daed7c 100644 --- a/tests/integration/translation/messages/bases/azure.py +++ b/tests/integration/translation/messages/bases/azure.py @@ -409,7 +409,7 @@ GPT_6_LUNA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_0534b0c3898d6d1b006ad023ba700c8195b63f1819ab327ab3", "model": "azure/gpt-6-luna", - "usage": {"input_tokens": 309, "output_tokens": 114}, + "usage": {"input_tokens": 309, "output_tokens": 114, "output_tokens_details": {"thinking_tokens": 50}}, "content": [ { "type": "text", @@ -543,7 +543,7 @@ GPT_6_1_SOL_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_0a4a896afe94ce65006ad023b047248193999d7c3a0c4fec39", "model": "azure/gpt-6.1-sol", - "usage": {"input_tokens": 309, "output_tokens": 137}, + "usage": {"input_tokens": 309, "output_tokens": 137, "output_tokens_details": {"thinking_tokens": 72}}, "content": [ { "type": "text", @@ -679,7 +679,7 @@ GPT_6_ASTRA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_019af305501e4822006ad023c3a50881958c4cf40623699cf8", "model": "azure/gpt-6-astra", - "usage": {"input_tokens": 309, "output_tokens": 138}, + "usage": {"input_tokens": 309, "output_tokens": 138, "output_tokens_details": {"thinking_tokens": 72}}, "content": [ { "type": "text", @@ -812,7 +812,7 @@ GPT_5_6_TERRA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_0d178c5d2401a624006ad023cf1c588194ab41d5803ed29401", "model": "azure/gpt-5.6-terra", - "usage": {"input_tokens": 309, "output_tokens": 92}, + "usage": {"input_tokens": 309, "output_tokens": 92, "output_tokens_details": {"thinking_tokens": 22}}, "content": [ { "type": "text", diff --git a/tests/integration/translation/messages/bases/bedrock_chat_completions.py b/tests/integration/translation/messages/bases/bedrock_chat_completions.py index 9a59f40a09d..061693dc8cc 100644 --- a/tests/integration/translation/messages/bases/bedrock_chat_completions.py +++ b/tests/integration/translation/messages/bases/bedrock_chat_completions.py @@ -55,7 +55,7 @@ GROK_4_7_TEST_CASE: Final = TranslationTestCase( "role": "assistant", "model": "bedrock/global.xai.grok-4.7", "stop_sequence": None, - "usage": {"input_tokens": 30, "output_tokens": 37}, + "usage": {"input_tokens": 30, "output_tokens": 37, "output_tokens_details": {"thinking_tokens": 27}}, "content": [{"type": "text", "text": "Hello."}], "stop_reason": "end_turn", "stop_details": None, diff --git a/tests/integration/translation/messages/bases/bedrock_converse.py b/tests/integration/translation/messages/bases/bedrock_converse.py index 6a115160fbc..38851b29178 100644 --- a/tests/integration/translation/messages/bases/bedrock_converse.py +++ b/tests/integration/translation/messages/bases/bedrock_converse.py @@ -277,7 +277,12 @@ CLAUDE_HAIKU_5_5_TEST_CASE: Final = TranslationTestCase( **BEDROCK_CONVERSE_MESSAGES_RESPONSE, "id": ANY, "model": "bedrock/converse/us.anthropic.claude-haiku-5-5", - "usage": {"input_tokens": 4, "output_tokens": 297, "cache_creation_input_tokens": 853}, + "usage": { + "input_tokens": 4, + "output_tokens": 297, + "cache_creation_input_tokens": 853, + "output_tokens_details": {"thinking_tokens": 147}, + }, "content": [ { "type": "thinking", @@ -403,7 +408,12 @@ CLAUDE_SONNET_5_5_TEST_CASE: Final = TranslationTestCase( **BEDROCK_CONVERSE_MESSAGES_RESPONSE, "id": ANY, "model": "bedrock/converse/us.anthropic.claude-sonnet-5-5", - "usage": {"input_tokens": 4, "output_tokens": 430, "cache_creation_input_tokens": 853}, + "usage": { + "input_tokens": 4, + "output_tokens": 430, + "cache_creation_input_tokens": 853, + "output_tokens_details": {"thinking_tokens": 155}, + }, "content": [ { "type": "thinking", @@ -504,7 +514,12 @@ CLAUDE_OPUS_5_5_TEST_CASE: Final = TranslationTestCase( **BEDROCK_CONVERSE_MESSAGES_RESPONSE, "id": ANY, "model": "bedrock/converse/us.anthropic.claude-opus-5-5", - "usage": {"input_tokens": 4, "output_tokens": 190, "cache_creation_input_tokens": 853}, + "usage": { + "input_tokens": 4, + "output_tokens": 190, + "cache_creation_input_tokens": 853, + "output_tokens_details": {"thinking_tokens": 35}, + }, "content": [ { "type": "thinking", diff --git a/tests/integration/translation/messages/bases/bedrock_mantle.py b/tests/integration/translation/messages/bases/bedrock_mantle.py index 7524bf8f7ea..d9c212d1220 100644 --- a/tests/integration/translation/messages/bases/bedrock_mantle.py +++ b/tests/integration/translation/messages/bases/bedrock_mantle.py @@ -534,7 +534,7 @@ GPT_6_1_SOL_TEST_CASE: Final = TranslationTestCase( **BEDROCK_MANTLE_OPENAI_RESPONSES_MESSAGES_RESPONSE, "id": "resp_n4higs7hpzdjowbay2ujeesbgcxavsl4pbkzhbh7pjpimjjaciaa", "model": "bedrock_mantle/openai.gpt-6.1-sol", - "usage": {"input_tokens": 309, "output_tokens": 141}, + "usage": {"input_tokens": 309, "output_tokens": 141, "output_tokens_details": {"thinking_tokens": 76}}, "content": [ { "type": "text", @@ -620,7 +620,7 @@ GPT_6_LUNA_TEST_CASE: Final = TranslationTestCase( **BEDROCK_MANTLE_OPENAI_RESPONSES_MESSAGES_RESPONSE, "id": "resp_di3jhnf3mehuuht6smddkuygfeo7rzxdxmwvrc52aigdygbts5ca", "model": "bedrock_mantle/openai.gpt-6-luna", - "usage": {"input_tokens": 309, "output_tokens": 119}, + "usage": {"input_tokens": 309, "output_tokens": 119, "output_tokens_details": {"thinking_tokens": 54}}, "content": [ { "type": "text", @@ -706,7 +706,7 @@ GPT_6_ASTRA_TEST_CASE: Final = TranslationTestCase( **BEDROCK_MANTLE_OPENAI_RESPONSES_MESSAGES_RESPONSE, "id": "resp_eznficwibpblmnocp4vuvti6d5bxboyxws4vqf5bk2lboyvvbx7q", "model": "bedrock_mantle/openai.gpt-6-astra", - "usage": {"input_tokens": 309, "output_tokens": 138}, + "usage": {"input_tokens": 309, "output_tokens": 138, "output_tokens_details": {"thinking_tokens": 73}}, "content": [ { "type": "text", @@ -793,7 +793,7 @@ GPT_5_6_TERRA_TEST_CASE: Final = TranslationTestCase( **BEDROCK_MANTLE_OPENAI_RESPONSES_MESSAGES_RESPONSE, "id": "resp_fqwklh5v6lnfftfaq6ddfbclshh3vkgmth75hufjjxjrhn726b2a", "model": "bedrock_mantle/openai.gpt-5.6-terra", - "usage": {"input_tokens": 309, "output_tokens": 109}, + "usage": {"input_tokens": 309, "output_tokens": 109, "output_tokens_details": {"thinking_tokens": 23}}, "content": [ { "type": "text", diff --git a/tests/integration/translation/messages/bases/gemini.py b/tests/integration/translation/messages/bases/gemini.py index 00d4a7fe5d2..55563559d20 100644 --- a/tests/integration/translation/messages/bases/gemini.py +++ b/tests/integration/translation/messages/bases/gemini.py @@ -152,6 +152,7 @@ GEMINI_3_8_FLASH_TEST_CASE: Final = TranslationTestCase( "usage": { "input_tokens": 354, "output_tokens": 419, + "output_tokens_details": {"thinking_tokens": 355}, }, }, ) @@ -286,6 +287,7 @@ GEMINI_3_1_PRO_PREVIEW_TEST_CASE: Final = TranslationTestCase( "usage": { "input_tokens": 354, "output_tokens": 302, + "output_tokens_details": {"thinking_tokens": 233}, }, }, ) @@ -416,6 +418,7 @@ GEMINI_4_ARGON_TEST_CASE: Final = TranslationTestCase( "usage": { "input_tokens": 412, "output_tokens": 192, + "output_tokens_details": {"thinking_tokens": 124}, }, }, ) @@ -467,7 +470,7 @@ GEMINI_3_5_FLASH_TEST_CASE: Final = TranslationTestCase( "role": "assistant", "model": "gemini/gemini-3.5-flash", "stop_sequence": None, - "usage": {"input_tokens": 10, "output_tokens": 127}, + "usage": {"input_tokens": 10, "output_tokens": 127, "output_tokens_details": {"thinking_tokens": 125}}, "content": [{"type": "text", "text": "Hello."}], "stop_reason": "end_turn", "stop_details": None, diff --git a/tests/integration/translation/messages/bases/openai.py b/tests/integration/translation/messages/bases/openai.py index f53baeafec4..503706935f0 100644 --- a/tests/integration/translation/messages/bases/openai.py +++ b/tests/integration/translation/messages/bases/openai.py @@ -394,7 +394,7 @@ GPT_6_LUNA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_bGl0ZWxsbTpjdXN0b21fbGxtX3Byb3ZpZGVyOm9wZW5haTttb2RlbF9pZDo0ZmY4OWU3OTgyMGFkZDVjMmM1ZmZlM2MyODk4ZDNkOGEzZDczY2IxY2ExN2JmZGMxYTRmNTRlZmFlNzhkMTIzO3Jlc3BvbnNlX2lkOnJlc3BfMDNlYzQ5MWM1Y2FhZTAyOTAwNmFkMDIzOWMwODM0ODdkMDk5YTIxZDYwYmU2YjVmZGU=", "model": "openai/gpt-6-luna", - "usage": {"input_tokens": 309, "output_tokens": 214}, + "usage": {"input_tokens": 309, "output_tokens": 214, "output_tokens_details": {"thinking_tokens": 150}}, "content": [ { "type": "redacted_thinking", @@ -517,7 +517,7 @@ GPT_6_1_SOL_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_bGl0ZWxsbTpjdXN0b21fbGxtX3Byb3ZpZGVyOm9wZW5haTttb2RlbF9pZDphMTYxN2NmYzNlODRjNzYxNDdjNTgxMmFhMjIwNGU0MDczOTM0M2YyMTg4YzQxNzIxMzRlOTc1ZTQ0YzQ3MjgyO3Jlc3BvbnNlX2lkOnJlc3BfMGFmNjdjYjc4YmVhZDJjNjAwNmFkMDIzYTcyNzM0ODdkMGIxNzY4Yjc3ZjY5NTZiNzM=", "model": "openai/gpt-6.1-sol", - "usage": {"input_tokens": 309, "output_tokens": 140}, + "usage": {"input_tokens": 309, "output_tokens": 140, "output_tokens_details": {"thinking_tokens": 75}}, "content": [ { "type": "redacted_thinking", @@ -640,7 +640,7 @@ GPT_6_ASTRA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_bGl0ZWxsbTpjdXN0b21fbGxtX3Byb3ZpZGVyOm9wZW5haTttb2RlbF9pZDphODRkZDk3MTcwNzk0ZDlhYzQ5ODhlZWMzOTJmN2Q5YjQwN2MyNDY1YmJhZTA4MGE1ZGYzMThlOTViOTA2NzlkO3Jlc3BvbnNlX2lkOnJlc3BfMDk2NGE0MWY1ODE1M2FjNTAwNmFkMDIzOGY5YmMwODdkMGE5ZTc5ZmRhMzc1YmVlM2M=", "model": "openai/gpt-6-astra", - "usage": {"input_tokens": 309, "output_tokens": 152}, + "usage": {"input_tokens": 309, "output_tokens": 152, "output_tokens_details": {"thinking_tokens": 87}}, "content": [ { "type": "redacted_thinking", @@ -764,7 +764,7 @@ GPT_5_6_TERRA_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_bGl0ZWxsbTpjdXN0b21fbGxtX3Byb3ZpZGVyOm9wZW5haTttb2RlbF9pZDo0Mjg5MWEzMzRjNTlhMjhlMjQ2MjQ0YTQ4YTJjOGVlMTM5NGYyZGNhZWMxYmVjZDhmZDk2N2NmNjYxNWNmZDM5O3Jlc3BvbnNlX2lkOnJlc3BfMDg1MjZjY2FiOWFhNjUzZTAwNmFkMDIzODg0YTFjODdkMDlhMjNmNmQ1YzYxZDY1ZDM=", "model": "openai/gpt-5.6-terra", - "usage": {"input_tokens": 309, "output_tokens": 107}, + "usage": {"input_tokens": 309, "output_tokens": 107, "output_tokens_details": {"thinking_tokens": 42}}, "content": [ { "type": "redacted_thinking", diff --git a/tests/integration/translation/messages/bases/openai_responses.py b/tests/integration/translation/messages/bases/openai_responses.py index f5a26684ea6..c63d21fa909 100644 --- a/tests/integration/translation/messages/bases/openai_responses.py +++ b/tests/integration/translation/messages/bases/openai_responses.py @@ -201,7 +201,7 @@ GPT_6_1_SOL_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "resp_bGl0ZWxsbTpjdXN0b21fbGxtX3Byb3ZpZGVyOm9wZW5haTttb2RlbF9pZDo3ZGE3NDY3Njc3N2IyZjVkYmQ3MDI1MTI1ZjEzMDdlYWQ0MjM3YTgzN2QxZTJmZDhkY2FkNjhlODM4ODU1Zjg2O3Jlc3BvbnNlX2lkOnJlc3BfMGFmNjdjYjc4YmVhZDJjNjAwNmFkMDIzYTcyNzM0ODdkMGIxNzY4Yjc3ZjY5NTZiNzM=", "model": "openai/responses/gpt-6.1-sol", - "usage": {"input_tokens": 309, "output_tokens": 140}, + "usage": {"input_tokens": 309, "output_tokens": 140, "output_tokens_details": {"thinking_tokens": 75}}, "content": [ { "type": "redacted_thinking", diff --git a/tests/integration/translation/messages/bases/vertex_ai.py b/tests/integration/translation/messages/bases/vertex_ai.py index 120b764a63c..10067668587 100644 --- a/tests/integration/translation/messages/bases/vertex_ai.py +++ b/tests/integration/translation/messages/bases/vertex_ai.py @@ -295,7 +295,7 @@ GEMINI_3_5_FLASH_TEST_CASE: Final = TranslationTestCase( "role": "assistant", "model": "vertex_ai/gemini-3.5-flash", "stop_sequence": None, - "usage": {"input_tokens": 9, "output_tokens": 86}, + "usage": {"input_tokens": 9, "output_tokens": 86, "output_tokens_details": {"thinking_tokens": 84}}, "content": [{"type": "text", "text": "Hello."}], "stop_reason": "end_turn", "stop_details": None, @@ -845,7 +845,7 @@ GEMINI_3_8_FLASH_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "ySjQarHoLb2apucPwee_YQ", "model": "vertex_ai/gemini-3.8-flash", - "usage": {"input_tokens": 242, "output_tokens": 314}, + "usage": {"input_tokens": 242, "output_tokens": 314, "output_tokens_details": {"thinking_tokens": 243}}, "content": [ { "type": "thinking", @@ -973,7 +973,7 @@ GEMINI_3_1_PRO_PREVIEW_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "xCjQasWfKcyL9LsPx7HAqQ8", "model": "vertex_ai/gemini-3.1-pro-preview", - "usage": {"input_tokens": 242, "output_tokens": 257}, + "usage": {"input_tokens": 242, "output_tokens": 257, "output_tokens_details": {"thinking_tokens": 198}}, "content": [ { "type": "thinking", @@ -1097,7 +1097,7 @@ GEMINI_4_ARGON_TEST_CASE: Final = TranslationTestCase( **MESSAGES_BRIDGE_RESPONSE, "id": "0CjQauKQPNyFpucPldSPiAY", "model": "vertex_ai/gemini-4-argon", - "usage": {"input_tokens": 412, "output_tokens": 192}, + "usage": {"input_tokens": 412, "output_tokens": 192, "output_tokens_details": {"thinking_tokens": 123}}, "content": [ { "type": "thinking", diff --git a/tests/integration/translation/messages/reasoning/test_messages_reasoning_gemini.py b/tests/integration/translation/messages/reasoning/test_messages_reasoning_gemini.py index c350d6e2a10..0c60d5d5e84 100644 --- a/tests/integration/translation/messages/reasoning/test_messages_reasoning_gemini.py +++ b/tests/integration/translation/messages/reasoning/test_messages_reasoning_gemini.py @@ -285,7 +285,7 @@ GEMINI_3_8_FLASH_THOUGHTS_OFF_TEST_CASE: Final = replace( "provider_specific_fields": {"signature": GEMINI_3_8_FLASH_THOUGHTS_OFF_THOUGHT_SIGNATURE}, }, ], - "usage": {"input_tokens": 354, "output_tokens": 385}, + "usage": {"input_tokens": 354, "output_tokens": 385, "output_tokens_details": {"thinking_tokens": 321}}, }, ) @@ -370,4 +370,5 @@ def test_a_streamed_turn_keeps_thinking_and_billing_reasoning_without_thought_te assert [object_value(event["usage"]) for event in events if event.get("type") == "message_delta"][-1] == { "input_tokens": 354, "output_tokens": 360, + "output_tokens_details": {"thinking_tokens": 295}, } diff --git a/tests/integration/translation/messages/tool_search/test_messages_tool_search_bedrock_chat_completions.py b/tests/integration/translation/messages/tool_search/test_messages_tool_search_bedrock_chat_completions.py index b457d9920bd..2d17e30a369 100644 --- a/tests/integration/translation/messages/tool_search/test_messages_tool_search_bedrock_chat_completions.py +++ b/tests/integration/translation/messages/tool_search/test_messages_tool_search_bedrock_chat_completions.py @@ -134,7 +134,7 @@ GROK_4_7_TOOL_SEARCH_TEST_CASE: Final = replace( expected_litellm_response={ **GROK_4_7_TEST_CASE.expected_litellm_response, "id": "chatcmpl-xevjeh2pxs6gjvcn2yz76piwdc4pi6fb2sqwb7psnwitm5higvvq", - "usage": {"input_tokens": 162, "output_tokens": 76}, + "usage": {"input_tokens": 162, "output_tokens": 76, "output_tokens_details": {"thinking_tokens": 57}}, "content": [{"type": "text", "text": "The web search tool is now loaded and available for use."}], }, ) diff --git a/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py b/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py index a62d2119e74..ac2b25a625a 100644 --- a/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py +++ b/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py @@ -3403,23 +3403,23 @@ def test_translate_openai_usage_to_anthropic_cache_tokens_from_dict_details_with def test_translate_openai_usage_to_anthropic_maps_reasoning_tokens_to_thinking_tokens(): - usage = Usage( + usage: Final = Usage( prompt_tokens=19, completion_tokens=30, total_tokens=49, completion_tokens_details=CompletionTokensDetailsWrapper(reasoning_tokens=25), ) - anthropic_usage = LiteLLMAnthropicMessagesAdapter.translate_openai_usage_to_anthropic_usage_delta(usage) + anthropic_usage: Final = LiteLLMAnthropicMessagesAdapter.translate_openai_usage_to_anthropic_usage_delta(usage) assert anthropic_usage["output_tokens"] == 30 assert anthropic_usage["output_tokens_details"] == {"thinking_tokens": 25} def test_translate_openai_usage_to_anthropic_omits_output_tokens_details_without_reasoning_tokens(): - usage = Usage(prompt_tokens=19, completion_tokens=5, total_tokens=24) + usage: Final = Usage(prompt_tokens=19, completion_tokens=5, total_tokens=24) - anthropic_usage = LiteLLMAnthropicMessagesAdapter.translate_openai_usage_to_anthropic_usage_delta(usage) + anthropic_usage: Final = LiteLLMAnthropicMessagesAdapter.translate_openai_usage_to_anthropic_usage_delta(usage) assert "output_tokens_details" not in anthropic_usage