fix(anthropic): accept integral float cache token counts

This commit is contained in:
Kannan Priyadharshan 2026-06-23 21:08:21 +08:00
parent 7e6dd8d460
commit c7591ba9c9
2 changed files with 101 additions and 1 deletions

View file

@ -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

View file

@ -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
# =====================================================================