Skip to content

Conversational Flow golden use case improvements - Anthropic total_tokens omits cache read/write tokens, undercounting cost #6768

Description

@lorenzejay

Description

For the Anthropic provider, total_tokens is computed as input_tokens + output_tokens only. Anthropic reports cache reads (cache_read_input_tokens) and cache writes (cache_creation_input_tokens) as separate counters that are not included in input_tokens — and both are billed (cache writes at 1.25×, cache reads at 0.1×).

The result: any prompt-cached request reports a total_tokens that is wildly lower than what was actually billed. Anyone computing cost from CrewAI's usage metrics undercounts, and the undercount grows with how well caching works.

This bit us while instrumenting per-session cost for a conversational Flow app — we ended up wrapping the Anthropic SDK ourselves to get real numbers. (Filing under the same banner as our other conversational-flow findings, though the bug itself is provider-level, not conversational-specific.)

Root cause

crewai/llms/providers/anthropic/completion.py ~L1971:

cache_read_tokens = getattr(usage, "cache_read_input_tokens", 0) or 0
cache_creation_tokens = (
    getattr(usage, "cache_creation_input_tokens", 0) or 0
)
result: dict[str, Any] = {
    "input_tokens": input_tokens,
    "output_tokens": output_tokens,
    "total_tokens": input_tokens + output_tokens,   # <-- omits both cache counters
    "cached_prompt_tokens": cache_read_tokens,
    "cache_creation_tokens": cache_creation_tokens,
}

Both cache values are captured correctly and surfaced individually — they're just excluded from the total.

Steps to Reproduce

  1. Make an Anthropic call large enough to cache, with a cache_breakpoint.
  2. Repeat it so the second call is a cache read.
  3. Compare total_tokens against the sum of all four counters.

Expected behavior

total_tokens should account for every billed token, i.e.

total_tokens = input_tokens + output_tokens + cache_read_tokens + cache_creation_tokens

(or, if total_tokens is deliberately meant to exclude cache traffic, that should be documented loudly, since it makes the field unusable for cost estimation on any cached workload.)

Screenshots/Code snippets

"""Minimal repro: total_tokens omits Anthropic cache read/write tokens."""
from crewai import LLM

llm = LLM(model="anthropic/claude-haiku-4-5", max_tokens=64)
BIG = "You are a helpful assistant.\n" + ("Filler context line for the cache. " * 500)

for i in (1, 2):
    llm.call([
        {"role": "system", "content": BIG, "cache_breakpoint": True},
        {"role": "user", "content": "Reply with the single word OK."},
    ])
    u = llm._token_usage
    billed = (u["prompt_tokens"] + u["completion_tokens"]
              + u["cached_prompt_tokens"] + u["cache_creation_tokens"])
    print(f"call {i}: reported total_tokens={u['total_tokens']:5} | "
          f"prompt={u['prompt_tokens']:5} completion={u['completion_tokens']:4} "
          f"cache_write={u['cache_creation_tokens']:5} cache_read={u['cached_prompt_tokens']:5} "
          f"-> actually billed={billed}")

Actual output (counters are cumulative across calls):

call 1: reported total_tokens=   17 | prompt=   13 completion=   4 cache_write= 4509 cache_read=    0 -> actually billed=4526
call 2: reported total_tokens=   34 | prompt=   26 completion=   8 cache_write= 4509 cache_read= 4509 -> actually billed=9052

total_tokens reports 17 where 4526 tokens were billed — a 266× undercount on the first call.

The same pattern is visible in production traces. An llm_call_completed event from one of our agent turns:

"usage": {
  "input_tokens": 2,
  "total_tokens": 42,
  "output_tokens": 40,
  "cached_prompt_tokens": 0,
  "cache_creation_tokens": 2285
}

2285 billed cache-write tokens, reported total of 42.

Environment

  • crewai 1.15.10
  • Python 3.12
  • macOS (Darwin 25.1)
  • Provider: anthropic (native provider path, not LiteLLM)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions