mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-07 06:51:41 +00:00
Compare commits
8 Commits
main
...
fix/anthro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e36aeef7cf | ||
|
|
e192c96cd2 | ||
|
|
ef97c33686 | ||
|
|
e5182e79a5 | ||
|
|
80d44851b9 | ||
|
|
a6161820d7 | ||
|
|
63d4202432 | ||
|
|
4089dae91d |
@@ -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()
|
||||
|
||||
@@ -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()`؛ قراءتها أثناء التنفيذ تُرجع المجموع الجزئي المتراكم حتى تلك اللحظة.
|
||||
|
||||
## إدارة حالة التدفق
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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()` 완료 후 언제든지 안전하게 읽을 수 있으며, 실행 중에 읽으면 그 시점까지 누적된 부분 합계를 반환합니다.
|
||||
|
||||
## 플로우 상태 관리
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user