From 4089dae91da0368dfbcdcf1d2a70d2d4502d2a7a Mon Sep 17 00:00:00 2001 From: ViditOstwal Date: Thu, 6 Aug 2026 20:10:40 +0530 Subject: [PATCH] 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. --- .../crewai/llms/providers/anthropic/completion.py | 14 +++++++++----- lib/crewai/tests/llms/anthropic/test_anthropic.py | 11 ++++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index 7deecbf60..cdeef692a 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -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, diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index fd21d3b8a..375246bb4 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -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