Fix Anthropic native provider to include cache tokens in input totals.

Anthropic reports cache read and cache creation separately from input_tokens; fold them into input_tokens and total_tokens so billed usage is not underreported on cached workloads.
This commit is contained in:
ViditOstwal
2026-08-06 20:10:40 +05:30
parent 18c52c4e1d
commit 4089dae91d
2 changed files with 17 additions and 8 deletions

View File

@@ -12,6 +12,7 @@ from crewai.llms.base_llm import BaseLLM, JsonResponseFormat, llm_call_context
from crewai.llms.hooks.base import BaseInterceptor
from crewai.llms.hooks.transport import AsyncHTTPTransport, HTTPTransport
from crewai.llms.providers.utils.common import safe_tool_conversion
from crewai.types.usage_metrics import _coerce_int
from crewai.utilities.agent_utils import is_context_length_exceeded
from crewai.utilities.exceptions.context_window_exceeding_exception import (
LLMContextLengthExceededError,
@@ -1965,12 +1966,15 @@ class AnthropicCompletion(BaseLLM):
"""Extract token usage and response metadata from Anthropic response."""
if hasattr(response, "usage") and response.usage:
usage = response.usage
input_tokens = getattr(usage, "input_tokens", 0)
output_tokens = getattr(usage, "output_tokens", 0)
cache_read_tokens = getattr(usage, "cache_read_input_tokens", 0) or 0
cache_creation_tokens = (
getattr(usage, "cache_creation_input_tokens", 0) or 0
input_tokens = _coerce_int(getattr(usage, "input_tokens", 0))
output_tokens = _coerce_int(getattr(usage, "output_tokens", 0))
cache_read_tokens = _coerce_int(
getattr(usage, "cache_read_input_tokens", 0)
)
cache_creation_tokens = _coerce_int(
getattr(usage, "cache_creation_input_tokens", 0)
)
input_tokens = input_tokens + cache_read_tokens + cache_creation_tokens
result: dict[str, Any] = {
"input_tokens": input_tokens,
"output_tokens": output_tokens,

View File

@@ -549,7 +549,12 @@ def test_anthropic_token_usage_tracking():
with patch.object(llm._client.messages, 'create') as mock_create:
mock_response = MagicMock()
mock_response.content = [MagicMock(text="test response")]
mock_response.usage = MagicMock(input_tokens=50, output_tokens=25)
mock_response.usage = MagicMock(
input_tokens=50,
output_tokens=25,
cache_read_input_tokens=0,
cache_creation_input_tokens=0,
)
mock_create.return_value = mock_response
result = llm.call("Hello")
@@ -1653,9 +1658,9 @@ def test_anthropic_cache_creation_tokens_extraction():
mock_response.model = None
usage = llm._extract_anthropic_token_usage(mock_response)
assert usage["input_tokens"] == 100
assert usage["input_tokens"] == 150
assert usage["output_tokens"] == 50
assert usage["total_tokens"] == 150
assert usage["total_tokens"] == 200
assert usage["cached_prompt_tokens"] == 30
assert usage["cache_creation_tokens"] == 20