Compare commits

...

8 Commits

Author SHA1 Message Date
ViditOstwal
e36aeef7cf Sync ar, ko, and pt-BR docs for Anthropic usage metrics updates.
Translate UsageMetrics semantics and Anthropic cache token accounting changes in crews, flows, and llms concept pages.
2026-08-07 00:24:59 +05:30
ViditOstwal
e192c96cd2 Apply ruff formatting to usage metrics helpers. 2026-08-06 20:43:49 +05:30
ViditOstwal
ef97c33686 Replace cross-page doc links with plain-text section references.
Avoid internal hyperlinks between concept pages for SEO; point readers to section names in prose instead.
2026-08-06 20:38:28 +05:30
ViditOstwal
e5182e79a5 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.
2026-08-06 20:35:07 +05:30
ViditOstwal
80d44851b9 Document Anthropic cache token accounting in LLM provider docs.
Explain how split Anthropic input counters map to UsageMetrics and link to the flows field semantics section.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 20:30:43 +05:30
ViditOstwal
a6161820d7 Document UsageMetrics token field semantics for flows and crews.
Clarify that total_tokens is prompt plus completion, breakdown fields are not additive, and Anthropic cache counters are folded into prompt_tokens.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 20:21:18 +05:30
ViditOstwal
63d4202432 Reconcile unreconciled Anthropic cache tokens in UsageMetrics.
LiteLLM and flow event paths can pass raw Anthropic usage where input_tokens excludes cache counters; fold them into prompt and total tokens without double-counting native provider payloads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 20:21:02 +05:30
ViditOstwal
4089dae91d 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.
2026-08-06 20:10:40 +05:30
16 changed files with 330 additions and 13 deletions

View File

@@ -172,6 +172,8 @@ class YourCrewName:
بعد تنفيذ الطاقم، يمكنك الوصول إلى خاصية `usage_metrics` لعرض مقاييس استخدام نموذج اللغة (LLM) لجميع المهام المنفذة.
`total_tokens` هو الإجمالي المفوتر (`prompt_tokens + completion_tokens`). حقول التفصيل مثل `cached_prompt_tokens` و`cache_creation_tokens` تصف أجزاءً مُدرجة بالفعل ضمن تلك الإجماليات ولا تُضاف مرة أخرى إلى `total_tokens`. راجع قسم **UsageMetrics field semantics** في توثيق مفهوم Flows للحصول على العقد الكامل.
```python Code
crew = Crew(agents=[agent1, agent2], tasks=[task1, task2])
crew.kickoff()

View File

@@ -266,6 +266,24 @@ print(flow.usage_metrics)
كلما احتجت إلى الإجمالي **الكامل** للتوكنات لتنفيذ التدفق.
</Note>
### دلالات حقول UsageMetrics
يستخدم كائن [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) المُعاد عقدًا محايدًا للمزود:
| الحقل | المعنى |
| --- | --- |
| `total_tokens` | الإجمالي المفوتر: `prompt_tokens + completion_tokens` |
| `prompt_tokens` | إجمالي رموز الإدخال/المطالبة المفوترة للطلب |
| `completion_tokens` | رموز الإخراج/الإكمال المفوترة للطلب |
| `cached_prompt_tokens` | جزء قراءة الذاكرة المؤقتة من رموز المطالبة (تفصيل فقط) |
| `cache_creation_tokens` | جزء كتابة الذاكرة المؤقتة من رموز المطالبة (تفصيل فقط، Anthropic) |
| `reasoning_tokens` | جزء التفكير/الاستدلال حيث يبلّغ المزود عنه بشكل منفصل (تفصيل فقط) |
| `successful_requests` | عدد استدعاءات LLM المُجمّعة |
حقول التفصيل مثل `cached_prompt_tokens` و`cache_creation_tokens` و`reasoning_tokens` **لا تُضاف** فوق `total_tokens` — بل تصف أجزاءً مُدرجة بالفعل ضمن `prompt_tokens` أو `completion_tokens`.
بالنسبة إلى Anthropic، تُدمج عدادات قراءة وكتابة الذاكرة المؤقتة ضمن `prompt_tokens`، لذا تنعكس أعباء العمل المخزنة مؤقتًا بالكامل في `total_tokens`. يُدرج مزودو OpenAI الرموز المخزنة مؤقتًا بالفعل داخل `prompt_tokens`؛ يعرض CrewAI الجزء المخزن مؤقتًا بشكل منفصل للوضوح.
كل حقل في [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) المُعاد هو مجموع جميع استدعاءات نموذج اللغة التي حدثت خلال استدعاء واحد لـ `flow.kickoff()`. تتم إعادة تعيين العدادات عند الاستدعاء التالي لـ `kickoff()` (وفي كل تكرار من `kickoff_for_each`)، لذلك لن تتكرر العدّات عبر التشغيلات المتتالية. يمكن قراءة هذه الخاصية بأمان في أي وقت بعد اكتمال `kickoff()`؛ قراءتها أثناء التنفيذ تُرجع المجموع الجزئي المتراكم حتى تلك اللحظة.
## إدارة حالة التدفق

View File

@@ -392,6 +392,22 @@ mode: "wide"
- تتبع استخدام الرموز
- محادثات استخدام أدوات متعددة الأدوار
**استخدام الرموز والتخزين المؤقت للمطالبة:**
يُبلّغ Anthropic عن الإدخال المفوتر في عدادات منفصلة — `input_tokens` (غير المخزن مؤقتًا)، و`cache_read_input_tokens`، و`cache_creation_input_tokens`. يدمج CrewAI الثلاثة ضمن `prompt_tokens` (و`input_tokens` الأصلي في استجابات المزود) بحيث يعكس `total_tokens` الاستخدام المفوتر الكامل على أعباء العمل المخزنة مؤقتًا.
يسجّل `cached_prompt_tokens` جزء قراءة الذاكرة المؤقتة كتفصيل فقط؛ وهو مُدرج بالفعل ضمن `prompt_tokens` ولا يجب إضافته مرة أخرى إلى `total_tokens`. يسجّل `cache_creation_tokens` عمليات الكتابة في الذاكرة المؤقتة بنفس الطريقة.
```python Code
usage = llm.get_token_usage_summary()
# total_tokens == prompt_tokens + completion_tokens
# prompt_tokens includes cache read + cache write for Anthropic
```
راجع قسم **UsageMetrics field semantics** في توثيق مفهوم Flows
للحصول على العقد المحايد للمزود المستخدم في `crew.usage_metrics`
و`flow.usage_metrics`.
**ملاحظات مهمة:**
- `max_tokens` معامل **مطلوب** لجميع نماذج Anthropic
- يستخدم Claude `stop_sequences` بدلاً من `stop`

View File

@@ -322,6 +322,8 @@ Caches can be employed to store the results of tools' execution, making the proc
After the crew execution, you can access the `usage_metrics` attribute to view the language model (LLM) usage metrics for all tasks executed by the crew. This provides insights into operational efficiency and areas for improvement.
`total_tokens` is the billed total (`prompt_tokens + completion_tokens`). Breakdown fields such as `cached_prompt_tokens` and `cache_creation_tokens` describe subsets already included in those totals and are not added on top of `total_tokens`. See the **UsageMetrics field semantics** section in the Flows concept documentation for the full contract.
```python Code
# Access the crew's usage metrics
crew = Crew(agents=[agent1, agent2], tasks=[task1, task2])

View File

@@ -267,7 +267,27 @@ print(flow.usage_metrics)
execution.
</Note>
Each entry in the returned [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) is the sum across all LLM calls made within a single `flow.kickoff()` invocation. Counters reset on the next `kickoff()` call (or on each iteration of `kickoff_for_each`), so successive runs don't double-count. The property is safe to read at any point after `kickoff()` completes; reading it during execution returns the partial total accumulated so far.
### UsageMetrics field semantics
The returned [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) object uses a provider-neutral contract:
| Field | Meaning |
| --- | --- |
| `total_tokens` | Billed total: `prompt_tokens + completion_tokens` |
| `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`, `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.
Each entry in the returned `UsageMetrics` is the sum across all LLM calls made within a single `flow.kickoff()` invocation. Counters reset on the next `kickoff()` call (or on each iteration of `kickoff_for_each`), so successive runs don't double-count. The property is safe to read at any point after `kickoff()` completes; reading it during execution returns the partial total accumulated so far.
## Flow State Management

View File

@@ -418,6 +418,27 @@ In this section, you'll find detailed examples that help you select, configure,
- Token usage tracking
- Multi-turn tool use conversations
**Token usage and prompt caching:**
Anthropic reports billed input in separate counters — `input_tokens` (uncached),
`cache_read_input_tokens`, and `cache_creation_input_tokens`. CrewAI folds all
three into `prompt_tokens` (and native `input_tokens` in provider responses) so
`total_tokens` reflects full billed usage on cached workloads.
`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`. `cache_creation_tokens` records cache writes the same way.
```python Code
usage = llm.get_token_usage_summary()
# total_tokens == prompt_tokens + completion_tokens
# prompt_tokens includes cache read + cache write for Anthropic
```
See the **UsageMetrics field semantics** section in the Flows concept
documentation for the provider-neutral contract used by `crew.usage_metrics`
and `flow.usage_metrics`.
**Important Notes:**
- `max_tokens` is a **required** parameter for all Anthropic models
- Claude uses `stop_sequences` instead of `stop`

View File

@@ -324,6 +324,8 @@ crew는 메모리(단기, 장기 및 엔티티 메모리)를 활용하여 시간
crew 실행 후, `usage_metrics` 속성에 접근하여 crew가 실행한 모든 작업에 대한 언어 모델(LLM) 사용 메트릭을 확인할 수 있습니다. 이를 통해 운영 효율성과 개선이 필요한 영역에 대한 인사이트를 얻을 수 있습니다.
`total_tokens`는 청구된 총합(`prompt_tokens + completion_tokens`)입니다. `cached_prompt_tokens` 및 `cache_creation_tokens`와 같은 breakdown 필드는 이미 해당 총합에 포함된 부분 집합을 설명하며 `total_tokens` 위에 다시 더하지 않습니다. 전체 계약은 Flows 개념 문서의 **UsageMetrics field semantics** 섹션을 참조하세요.
```python Code
# Access the crew's usage metrics
crew = Crew(agents=[agent1, agent2], tasks=[task1, task2])

View File

@@ -261,6 +261,24 @@ print(flow.usage_metrics)
**전체** 토큰 집계가 필요할 때는 항상 `flow.usage_metrics`를 사용하십시오.
</Note>
### UsageMetrics 필드 의미
반환되는 [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) 객체는 제공자 중립 계약을 사용합니다:
| 필드 | 의미 |
| --- | --- |
| `total_tokens` | 청구된 총합: `prompt_tokens + completion_tokens` |
| `prompt_tokens` | 요청에 대해 청구된 전체 입력/프롬프트 토큰 |
| `completion_tokens` | 요청에 대해 청구된 출력/완료 토큰 |
| `cached_prompt_tokens` | 프롬프트 토큰 중 캐시 읽기 부분 집합 (breakdown 전용) |
| `cache_creation_tokens` | 프롬프트 토큰 중 캐시 쓰기 부분 집합 (breakdown 전용, Anthropic) |
| `reasoning_tokens` | 제공자가 별도로 보고하는 추론/사고 부분 집합 (breakdown 전용) |
| `successful_requests` | 집계된 LLM 호출 수 |
`cached_prompt_tokens`, `cache_creation_tokens`, `reasoning_tokens`와 같은 breakdown 필드는 `total_tokens` **위에 추가되지 않습니다** — 이미 `prompt_tokens` 또는 `completion_tokens`에 포함된 부분을 설명합니다.
Anthropic의 경우 캐시 읽기 및 쓰기 카운터가 `prompt_tokens`에 포함되므로, 캐시된 워크로드가 `total_tokens`에 완전히 반영됩니다. OpenAI 스타일 제공자는 캐시된 입력을 이미 `prompt_tokens`에 포함합니다. CrewAI는 가시성을 위해 캐시된 부분을 별도로 표시합니다.
반환되는 [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py)의 각 항목은 단일 `flow.kickoff()` 실행 동안 발생한 모든 LLM 호출의 합계입니다. 다음 `kickoff()` 호출(및 `kickoff_for_each`의 각 반복)에서 카운터가 초기화되므로 연속 실행이 이중으로 집계되지 않습니다. 이 속성은 `kickoff()` 완료 후 언제든지 안전하게 읽을 수 있으며, 실행 중에 읽으면 그 시점까지 누적된 부분 합계를 반환합니다.
## 플로우 상태 관리

View File

@@ -270,6 +270,21 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
)
```
**토큰 사용량 및 프롬프트 캐싱:**
Anthropic은 청구된 입력을 별도 카운터로 보고합니다 — `input_tokens`(캐시되지 않은 입력), `cache_read_input_tokens`, `cache_creation_input_tokens`. CrewAI는 세 값을 모두 `prompt_tokens`(및 제공자 응답의 네이티브 `input_tokens`)에 포함시켜 캐시된 워크로드에서 `total_tokens`가 전체 청구 사용량을 반영하도록 합니다.
`cached_prompt_tokens`는 캐시 읽기 부분을 breakdown으로만 기록합니다. 이미 `prompt_tokens`에 포함되어 있으므로 `total_tokens`에 다시 더하면 안 됩니다. `cache_creation_tokens`도 캐시 쓰기를 같은 방식으로 기록합니다.
```python Code
usage = llm.get_token_usage_summary()
# total_tokens == prompt_tokens + completion_tokens
# prompt_tokens includes cache read + cache write for Anthropic
```
`crew.usage_metrics` 및 `flow.usage_metrics`에 사용되는 제공자 중립 계약은
Flows 개념 문서의 **UsageMetrics field semantics** 섹션을 참조하세요.
현재 모델 ID와 기능은 Anthropic의 [모델 개요](https://platform.claude.com/docs/en/about-claude/models/overview)를 확인하고, 프로덕션에서 모델을 고정하기 전에 [모델 지원 중단 표](https://platform.claude.com/docs/en/about-claude/model-deprecations)를 검토하세요.
</Accordion>

View File

@@ -322,6 +322,8 @@ Caches podem ser utilizados para armazenar resultados de execuções de ferramen
Após a execução da crew, você pode acessar o atributo `usage_metrics` para visualizar as métricas de uso do modelo de linguagem (LLM) para todas as tasks executadas pela crew. Isso fornece insights sobre eficiência operacional e oportunidades de melhoria.
`total_tokens` é o total faturado (`prompt_tokens + completion_tokens`). Campos de breakdown como `cached_prompt_tokens` e `cache_creation_tokens` descrevem subconjuntos já incluídos nesses totais e não são somados novamente a `total_tokens`. Consulte a seção **UsageMetrics field semantics** na documentação do conceito Flows para o contrato completo.
```python Code
# Acessar as métricas de uso da crew
crew = Crew(agents=[agent1, agent2], tasks=[task1, task2])

View File

@@ -260,6 +260,24 @@ print(flow.usage_metrics)
rollup **completo** de tokens da execução do Flow.
</Note>
### Semântica dos campos UsageMetrics
O objeto [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) retornado usa um contrato neutro em relação ao provedor:
| Campo | Significado |
| --- | --- |
| `total_tokens` | Total faturado: `prompt_tokens + completion_tokens` |
| `prompt_tokens` | Total de tokens de entrada/prompt faturados para a requisição |
| `completion_tokens` | Tokens de saída/conclusão faturados para a requisição |
| `cached_prompt_tokens` | Subconjunto de leitura de cache dos tokens de prompt (apenas breakdown) |
| `cache_creation_tokens` | Subconjunto de escrita de cache dos tokens de prompt (apenas breakdown, Anthropic) |
| `reasoning_tokens` | Subconjunto de raciocínio/pensamento quando o provedor reporta separadamente (apenas breakdown) |
| `successful_requests` | Número de chamadas LLM agregadas |
Campos de breakdown como `cached_prompt_tokens`, `cache_creation_tokens` e `reasoning_tokens` **não** são somados sobre `total_tokens` — eles descrevem porções já incluídas em `prompt_tokens` ou `completion_tokens`.
Para Anthropic, os contadores de leitura e escrita de cache são incorporados em `prompt_tokens`, de modo que workloads em cache são totalmente refletidos em `total_tokens`. Provedores no estilo OpenAI já incluem a entrada em cache dentro de `prompt_tokens`; o CrewAI expõe a porção em cache separadamente para visibilidade.
Cada campo do [`UsageMetrics`](https://github.com/crewAIInc/crewAI/blob/main/lib/crewai/src/crewai/types/usage_metrics.py) retornado representa a soma de todas as chamadas de LLM feitas em uma única invocação de `flow.kickoff()`. Os contadores são resetados a cada novo `kickoff()` (e em cada iteração de `kickoff_for_each`), de modo que execuções sucessivas não duplicam o total. A propriedade é segura para ser lida em qualquer momento após o `kickoff()`; lê-la durante a execução retorna o total parcial acumulado até aquele instante.
## Gerenciamento de Estado em Flows

View File

@@ -270,6 +270,22 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
)
```
**Uso de tokens e prompt caching:**
A Anthropic reporta a entrada faturada em contadores separados — `input_tokens` (não em cache), `cache_read_input_tokens` e `cache_creation_input_tokens`. O CrewAI incorpora os três em `prompt_tokens` (e no `input_tokens` nativo nas respostas do provedor) para que `total_tokens` reflita o uso faturado completo em workloads em cache.
`cached_prompt_tokens` registra a porção de leitura de cache apenas como breakdown; ela já está incluída em `prompt_tokens` e não deve ser somada novamente a `total_tokens`. `cache_creation_tokens` registra escritas de cache da mesma forma.
```python Code
usage = llm.get_token_usage_summary()
# total_tokens == prompt_tokens + completion_tokens
# prompt_tokens includes cache read + cache write for Anthropic
```
Consulte a seção **UsageMetrics field semantics** na documentação do
conceito Flows para o contrato neutro em relação ao provedor usado por
`crew.usage_metrics` e `flow.usage_metrics`.
Consulte a [visão geral dos modelos](https://platform.claude.com/docs/en/about-claude/models/overview) da Anthropic para obter IDs e capacidades atuais e revise a [tabela de descontinuação](https://platform.claude.com/docs/en/about-claude/model-deprecations) antes de fixar um modelo em produção.
</Accordion>

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

@@ -108,6 +108,37 @@ class UsageMetrics(BaseModel):
),
)
@staticmethod
def _has_unreconciled_anthropic_cache_keys(usage_data: dict[str, Any]) -> bool:
"""Detect raw Anthropic usage that still splits cache from ``input_tokens``.
The native ``AnthropicCompletion`` provider folds cache read/creation
counters into ``input_tokens`` before usage reaches this normalizer.
LiteLLM and flow-level event aggregation can still deliver the raw
Anthropic API shape, where ``input_tokens`` is only the uncached
portion and cache counters arrive as separate keys. Without
reconciling here, ``prompt_tokens`` and ``total_tokens`` undercount
billed usage on cached Anthropic workloads.
"""
return "input_tokens" in usage_data and (
"cache_read_input_tokens" in usage_data
or "cache_creation_input_tokens" in usage_data
)
@staticmethod
def _resolve_billed_prompt_tokens(usage_data: dict[str, Any]) -> int:
"""Return the full billed prompt/input token count for a usage dict."""
if UsageMetrics._has_unreconciled_anthropic_cache_keys(usage_data):
return (
_coerce_int(usage_data.get("input_tokens"))
+ _coerce_int(usage_data.get("cache_read_input_tokens"))
+ _coerce_int(usage_data.get("cache_creation_input_tokens"))
)
return _first_int(
usage_data, "prompt_tokens", "prompt_token_count", "input_tokens"
)
@classmethod
def from_provider_dict(cls, usage_data: dict[str, Any] | None) -> Self | None:
"""Normalize a provider's raw usage dict into a ``UsageMetrics``.
@@ -125,9 +156,7 @@ class UsageMetrics(BaseModel):
if not usage_data:
return None
prompt_tokens = _first_int(
usage_data, "prompt_tokens", "prompt_token_count", "input_tokens"
)
prompt_tokens = cls._resolve_billed_prompt_tokens(usage_data)
completion_tokens = _first_int(
usage_data,
"completion_tokens",
@@ -145,12 +174,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=_coerce_int(usage_data.get("cache_creation_tokens")),
cache_creation_tokens=cache_creation_tokens,
successful_requests=1,
)

View File

@@ -297,3 +297,128 @@ class TestUsageMetricsNewFields:
dumped = metrics.model_dump()
assert dumped["reasoning_tokens"] == 10
assert dumped["cache_creation_tokens"] == 5
class TestFromProviderDictAnthropicCacheTokens:
def test_cache_read_tokens_included_in_prompt_and_total(self):
from crewai.types.usage_metrics import UsageMetrics
metrics = UsageMetrics.from_provider_dict(
{
"input_tokens": 3,
"output_tokens": 44,
"cache_read_input_tokens": 2061,
}
)
assert metrics is not None
assert metrics.prompt_tokens == 2064
assert metrics.completion_tokens == 44
assert metrics.total_tokens == 2108
assert metrics.cached_prompt_tokens == 2061
def test_cache_creation_tokens_included_in_prompt_and_total(self):
from crewai.types.usage_metrics import UsageMetrics
metrics = UsageMetrics.from_provider_dict(
{
"input_tokens": 100,
"output_tokens": 50,
"cache_creation_input_tokens": 20,
}
)
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
metrics = UsageMetrics.from_provider_dict(
{
"input_tokens": 100,
"output_tokens": 50,
"cache_read_input_tokens": 30,
"cache_creation_input_tokens": 20,
}
)
assert metrics is not None
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
metrics = UsageMetrics.from_provider_dict(
{"input_tokens": 100, "output_tokens": 50}
)
assert metrics is not None
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
metrics = UsageMetrics.from_provider_dict(
{
"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
metrics = UsageMetrics.from_provider_dict(
{
"prompt_tokens": 100,
"completion_tokens": 50,
"prompt_tokens_details": {"cached_tokens": 30},
}
)
assert metrics is not None
assert metrics.prompt_tokens == 100
assert metrics.total_tokens == 150
assert metrics.cached_prompt_tokens == 30
def test_cumulative_usage_via_add_usage_metrics(self):
from crewai.types.usage_metrics import UsageMetrics
first = UsageMetrics.from_provider_dict(
{
"input_tokens": 100,
"output_tokens": 50,
"cache_read_input_tokens": 30,
}
)
second = UsageMetrics.from_provider_dict(
{
"input_tokens": 40,
"output_tokens": 20,
}
)
assert first is not None and second is not None
first.add_usage_metrics(second)
assert first.prompt_tokens == 170
assert first.completion_tokens == 70
assert first.total_tokens == 240
assert first.cached_prompt_tokens == 30
assert first.successful_requests == 2

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