Restore cache_creation_tokens breakdown in UsageMetrics normalization.

Keep cache writes as a separate breakdown field while they remain folded into prompt_tokens for billed totals.
This commit is contained in:
ViditOstwal
2026-08-06 20:35:07 +05:30
parent 80d44851b9
commit e5182e79a5
5 changed files with 17 additions and 2 deletions

View File

@@ -277,10 +277,13 @@ The returned [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/
| `prompt_tokens` | Full input/prompt tokens billed for the request |
| `completion_tokens` | Output/completion tokens billed for the request |
| `cached_prompt_tokens` | Cache-read subset of prompt tokens (breakdown only) |
| `cache_creation_tokens` | Cache-write subset of prompt tokens (breakdown only, Anthropic) |
| `reasoning_tokens` | Reasoning/thinking subset where the provider reports it separately (breakdown only) |
| `successful_requests` | Number of LLM calls aggregated |
Breakdown fields such as `cached_prompt_tokens` and `reasoning_tokens` are **not** added on top of `total_tokens` — they describe portions already included in `prompt_tokens` or `completion_tokens`.
Breakdown fields such as `cached_prompt_tokens`, `cache_creation_tokens`, and
`reasoning_tokens` are **not** added on top of `total_tokens` — they describe
portions already included in `prompt_tokens` or `completion_tokens`.
For Anthropic, cache read and cache write counters are folded into `prompt_tokens`, so cached workloads are fully reflected in `total_tokens`. OpenAI-style providers already include cached input inside `prompt_tokens`; CrewAI surfaces the cached portion separately for visibility.

View File

@@ -427,7 +427,7 @@ In this section, you'll find detailed examples that help you select, configure,
`cached_prompt_tokens` records the cache-read portion as a breakdown only; it is
already included in `prompt_tokens` and must not be added again to
`total_tokens`.
`total_tokens`. `cache_creation_tokens` records cache writes the same way.
```python Code
usage = llm.get_token_usage_summary()

View File

@@ -177,11 +177,16 @@ class UsageMetrics(BaseModel):
if isinstance(details, dict):
cached_prompt_tokens = _coerce_int(details.get("cached_tokens"))
cache_creation_tokens = _coerce_int(
usage_data.get("cache_creation_tokens")
) or _coerce_int(usage_data.get("cache_creation_input_tokens"))
return cls(
total_tokens=prompt_tokens + completion_tokens,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
cached_prompt_tokens=cached_prompt_tokens,
reasoning_tokens=_coerce_int(usage_data.get("reasoning_tokens")),
cache_creation_tokens=cache_creation_tokens,
successful_requests=1,
)

View File

@@ -331,6 +331,7 @@ class TestFromProviderDictAnthropicCacheTokens:
assert metrics is not None
assert metrics.prompt_tokens == 120
assert metrics.total_tokens == 170
assert metrics.cache_creation_tokens == 20
def test_cache_read_and_creation_tokens_both_included(self):
from crewai.types.usage_metrics import UsageMetrics
@@ -348,6 +349,7 @@ class TestFromProviderDictAnthropicCacheTokens:
assert metrics.prompt_tokens == 150
assert metrics.total_tokens == 200
assert metrics.cached_prompt_tokens == 30
assert metrics.cache_creation_tokens == 20
def test_missing_cache_fields_preserve_non_cached_totals(self):
from crewai.types.usage_metrics import UsageMetrics
@@ -360,6 +362,7 @@ class TestFromProviderDictAnthropicCacheTokens:
assert metrics.prompt_tokens == 100
assert metrics.total_tokens == 150
assert metrics.cached_prompt_tokens == 0
assert metrics.cache_creation_tokens == 0
def test_reconciled_native_dict_is_not_double_counted(self):
from crewai.types.usage_metrics import UsageMetrics
@@ -369,12 +372,14 @@ class TestFromProviderDictAnthropicCacheTokens:
"input_tokens": 150,
"output_tokens": 50,
"cached_prompt_tokens": 30,
"cache_creation_tokens": 20,
}
)
assert metrics is not None
assert metrics.prompt_tokens == 150
assert metrics.total_tokens == 200
assert metrics.cache_creation_tokens == 20
def test_openai_cached_prompt_tokens_are_not_added_twice(self):
from crewai.types.usage_metrics import UsageMetrics

View File

@@ -130,6 +130,7 @@ class TestUsageDictToMetrics:
"total_tokens": 180,
"cached_prompt_tokens": 40,
"reasoning_tokens": 25,
"cache_creation_tokens": 10,
},
UsageMetrics(
prompt_tokens=100,
@@ -137,6 +138,7 @@ class TestUsageDictToMetrics:
total_tokens=180,
cached_prompt_tokens=40,
reasoning_tokens=25,
cache_creation_tokens=10,
successful_requests=1,
),
),