mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-10 08:21:54 +00:00
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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user