[Bug Fix] Anthropic - Token Usage Null Handling in calculate_usage (#12068)

* [Bug Fix] Anthropic - Token Usage Null Handling in calculate_usage (BerriAI/litellm#11920)

* [Fix] Missed a null check and used a cast instead by error
This commit is contained in:
Kishan 2025-06-27 18:00:23 +01:00 • committed by GitHub
parent 8bd1f8f6ab
commit fc17da0aef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 7 deletions

View file

@ -708,7 +708,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
_litellm_metadata
and isinstance(_litellm_metadata, dict)
and "user_id" in _litellm_metadata
and not _valid_user_id(_litellm_metadata.get("user_id", None))
and _litellm_metadata["user_id"] is not None
and not _valid_user_id(_litellm_metadata["user_id"])
):
optional_params["metadata"] = {"user_id": _litellm_metadata["user_id"]}
@ -805,19 +806,29 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
def calculate_usage(
self, usage_object: dict, reasoning_content: Optional[str]
) -> Usage:
prompt_tokens = usage_object.get("input_tokens", 0)
completion_tokens = usage_object.get("output_tokens", 0)
# NOTE: Sometimes the usage object has None set explicitly for token counts, meaning .get() & key access returns None, and we need to account for this
prompt_tokens = usage_object.get("input_tokens", 0) or 0
completion_tokens = usage_object.get("output_tokens", 0) or 0
_usage = usage_object
cache_creation_input_tokens: int = 0
cache_read_input_tokens: int = 0
web_search_requests: Optional[int] = None
if "cache_creation_input_tokens" in _usage:
if (
"cache_creation_input_tokens" in _usage
and _usage["cache_creation_input_tokens"] is not None
):
cache_creation_input_tokens = _usage["cache_creation_input_tokens"]
if "cache_read_input_tokens" in _usage:
if (
"cache_read_input_tokens" in _usage
and _usage["cache_read_input_tokens"] is not None
):
cache_read_input_tokens = _usage["cache_read_input_tokens"]
prompt_tokens += cache_read_input_tokens
if "server_tool_use" in _usage:
if "web_search_requests" in _usage["server_tool_use"]:
if "server_tool_use" in _usage and _usage["server_tool_use"] is not None:
if (
"web_search_requests" in _usage["server_tool_use"]
and _usage["server_tool_use"]["web_search_requests"] is not None
):
web_search_requests = cast(
int, _usage["server_tool_use"]["web_search_requests"]
)

View file

@ -11,6 +11,7 @@ sys.path.insert(
from unittest.mock import MagicMock, patch
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
from litellm.types.utils import PromptTokensDetailsWrapper, ServerToolUse
def test_response_format_transformation_unit_test():
@ -57,6 +58,83 @@ def test_calculate_usage():
assert usage._cache_creation_input_tokens == 12304
assert usage._cache_read_input_tokens == 0
@pytest.mark.parametrize("usage_object,expected_usage", [
[
{
"cache_creation_input_tokens": None,
"cache_read_input_tokens": None,
"input_tokens": None,
"output_tokens": 43,
"server_tool_use": None
},
{
"prompt_tokens": 0,
"completion_tokens": 43,
"total_tokens": 43,
"_cache_creation_input_tokens": 0,
"_cache_read_input_tokens": 0
}
],
[
{
"cache_creation_input_tokens": 100,
"cache_read_input_tokens": 200,
"input_tokens": 1,
"output_tokens": None,
"server_tool_use": None
},
{
"prompt_tokens": 1 + 200,
"completion_tokens": 0,
"total_tokens": 1 + 200,
"_cache_creation_input_tokens": 100,
"_cache_read_input_tokens": 200,
}
],
[
{
"server_tool_use": {
"web_search_requests": 10
}
},
{
"server_tool_use": ServerToolUse(web_search_requests=10)
}
]
])
def test_calculate_usage_nulls(usage_object, expected_usage):
"""
Correctly deal with null values in usage object
Fixes https://github.com/BerriAI/litellm/issues/11920
"""
config = AnthropicConfig()
usage = config.calculate_usage(usage_object=usage_object, reasoning_content=None)
for k, v in expected_usage.items():
assert hasattr(usage, k)
assert getattr(usage, k) == v
@pytest.mark.parametrize("usage_object", [
{
"server_tool_use": {
"web_search_requests": None
}
},
{
"server_tool_use": None
}
])
def test_calculate_usage_server_tool_null(usage_object):
"""
Correctly deal with null values in usage object
Fixes https://github.com/BerriAI/litellm/issues/11920
"""
config = AnthropicConfig()
usage = config.calculate_usage(usage_object=usage_object, reasoning_content=None)
assert not hasattr(usage, "server_tool_use")
def test_extract_response_content_with_citations():
config = AnthropicConfig()