Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions litellm/llms/anthropic/chat/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2214,18 +2214,33 @@ def calculate_usage(
if "inference_geo" in _usage and _usage["inference_geo"] is not None:
inference_geo = _usage["inference_geo"]

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"]
prompt_tokens += cache_creation_input_tokens
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
iterations: Optional[List[Any]] = _usage.get("iterations")
if iterations:
prompt_tokens = sum(it.get("input_tokens", 0) or 0 for it in iterations)
completion_tokens = sum(
it.get("output_tokens", 0) or 0 for it in iterations
)
cache_creation_input_tokens = sum(
it.get("cache_creation_input_tokens", 0) or 0 for it in iterations
)
cache_read_input_tokens = sum(
it.get("cache_read_input_tokens", 0) or 0 for it in iterations
)
prompt_tokens += cache_creation_input_tokens + cache_read_input_tokens

if not iterations:
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"]
prompt_tokens += cache_creation_input_tokens
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 and _usage["server_tool_use"] is not None:
if (
"web_search_requests" in _usage["server_tool_use"]
Expand Down Expand Up @@ -2264,7 +2279,9 @@ def calculate_usage(
),
)

raw_input_tokens = usage_object.get("input_tokens", 0) or 0
raw_input_tokens = (
prompt_tokens - cache_read_input_tokens - cache_creation_input_tokens
)
prompt_tokens_details = PromptTokensDetailsWrapper(
cached_tokens=cache_read_input_tokens,
cache_creation_tokens=cache_creation_input_tokens,
Expand Down Expand Up @@ -2296,6 +2313,7 @@ def calculate_usage(
cache_creation_input_tokens=cache_creation_input_tokens,
cache_read_input_tokens=cache_read_input_tokens,
completion_tokens_details=completion_token_details,
iterations=iterations,
server_tool_use=(
ServerToolUse(
web_search_requests=web_search_requests,
Expand Down
96 changes: 96 additions & 0 deletions tests/test_anthropic_compaction_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from litellm.llms.anthropic.chat.transformation import AnthropicConfig


def test_anthropic_compaction_usage_calculation():
"""
Test that calculate_usage correctly sums tokens from the iterations array
as requested in Issue #27060.
"""
anthropic_config = AnthropicConfig()

# Mock usage object with compaction iterations
usage_object = {
"input_tokens": 100, # Top-level (excludes compaction)
"output_tokens": 50, # Top-level (excludes compaction)
"iterations": [
{
"iteration": 1,
"type": "compaction",
"input_tokens": 1000,
"output_tokens": 500,
},
{
"iteration": 2,
"type": "message",
"input_tokens": 100,
"output_tokens": 50,
},
],
}

usage = anthropic_config.calculate_usage(
usage_object=usage_object, reasoning_content=None
)

# Assertions
# Total prompt tokens should be 1000 + 100 = 1100
assert usage.prompt_tokens == 1100
# Total completion tokens should be 500 + 50 = 550
assert usage.completion_tokens == 550
# Total tokens should be 1650
assert usage.total_tokens == 1650

# Assert details
assert usage.prompt_tokens_details.text_tokens == 1100

# Assert iterations passthrough
assert usage.iterations is not None
assert len(usage.iterations) == 2
assert usage.iterations[0]["type"] == "compaction"


def test_anthropic_compaction_usage_with_iteration_cache():
"""
Test that calculate_usage correctly sums caching tokens FROM iterations.
This covers the specific case mentioned by JasonPan.
"""
anthropic_config = AnthropicConfig()

usage_object = {
"input_tokens": 100,
"output_tokens": 50,
"iterations": [
{
"type": "compaction",
"input_tokens": 500,
"output_tokens": 200,
"cache_creation_input_tokens": 50,
"cache_read_input_tokens": 17000,
},
{
"type": "message",
"input_tokens": 100,
"output_tokens": 50,
"cache_creation_input_tokens": 10,
"cache_read_input_tokens": 20,
},
],
}

usage = anthropic_config.calculate_usage(
usage_object=usage_object, reasoning_content=None
)

# input_tokens sum = 500 + 100 = 600
# cache_creation sum = 50 + 10 = 60
# cache_read sum = 17000 + 20 = 17020
# Total prompt tokens = 600 + 60 + 17020 = 17680
assert usage.prompt_tokens == 17680
assert usage.completion_tokens == 250
assert usage.prompt_tokens_details.cache_creation_tokens == 60
assert usage.prompt_tokens_details.cached_tokens == 17020


if __name__ == "__main__":
test_anthropic_compaction_usage_calculation()
test_anthropic_compaction_usage_with_iteration_cache()
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ def test_ui_view_request_response_forbids_non_admin_without_db(client, monkeypat
"metadata.additional_usage_values.cache_read_input_tokens",
"metadata.additional_usage_values.inference_geo",
"metadata.additional_usage_values.speed",
"metadata.additional_usage_values.iterations",
"metadata.litellm_overhead_time_ms",
"metadata.cost_breakdown",
"metadata.user_api_key",
Expand Down
Loading