Skip to content

Commit 69c3934

Browse files
Hakan Bogan Personalclaude
authored andcommitted
fix: capture crewai token usage from CrewOutput.token_usage
CrewAI 1.6.x routes through litellm and silently discards LangChain callbacks when converting a passed ChatAnthropic / ChatOpenAI into CrewAI's native LLM (see crewai/agent/core.py:create_llm — it copies only model/temperature/api_key, never callbacks). As a result the framework's TokenUsageCallback never fired, AgentDecision.tokens_used was always 0, and token_cost_estimate was always $0.0000. Add BaseAgent._extract_tokens helper that reads the canonical source — CrewOutput.token_usage.total_tokens — populated by Crew.calculate_usage_metrics after every kickoff(). Wire it into all 6 LLM-driven agents (CCA, Logic, Performance, Documentation, TestCoverage, RevisionProposer) and drop the no-op callbacks=[self.token_callback] arg + dead reset/get_token_count calls. TokenUsageCallback itself is left in place as harmless dead code; a follow-up commit can remove it together with the constructor wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 42d9a3a commit 69c3934

7 files changed

Lines changed: 28 additions & 68 deletions

agents/base.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ def _get_token_count(self) -> int:
119119
"""Get total tokens used from callback."""
120120
return self.token_callback.total_tokens
121121

122+
@staticmethod
123+
def _extract_tokens(crew_result: Any) -> int:
124+
"""Read aggregated tokens from a CrewAI Crew.kickoff() result.
125+
126+
CrewAI 1.6.x routes through litellm and silently drops langchain
127+
callbacks during Agent LLM conversion (see crewai/agent/core.py:
128+
create_llm), so the framework's TokenUsageCallback never fires.
129+
CrewOutput.token_usage (UsageMetrics) is the reliable source —
130+
total_tokens aggregates input + output across every LLM call
131+
inside the Crew.
132+
"""
133+
token_usage = getattr(crew_result, "token_usage", None)
134+
if token_usage is not None:
135+
return int(getattr(token_usage, "total_tokens", 0))
136+
return 0
137+
122138
def _validate_findings(self, findings: List[Finding]) -> List[Finding]:
123139
"""Validate findings meet evidence requirements."""
124140
valid = []

agents/change_context_analyst.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,10 @@ def _build_analysis_context(self, context: PRContext, prompt_template: str) -> s
7676
def _execute_task(self, agent: Agent, task: Task) -> dict:
7777
"""Execute CrewAI task and extract results."""
7878
try:
79-
self._reset_token_tracking()
80-
# Pass callback to Crew to track token usage
81-
crew = Crew(
82-
agents=[agent],
83-
tasks=[task],
84-
verbose=False,
85-
callbacks=[self.token_callback]
86-
)
79+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
8780
result = crew.kickoff()
8881
output = str(result) if result else ""
89-
tokens = self._get_token_count()
90-
# Fallback: if callback didn't capture tokens, try to get from result
91-
if tokens == 0 and hasattr(result, 'usage_metadata'):
92-
if hasattr(result.usage_metadata, 'total_tokens'):
93-
tokens = result.usage_metadata.total_tokens
82+
tokens = self._extract_tokens(result)
9483
return {
9584
"reasoning": output[:500] or "Analysis completed",
9685
"tokens": tokens,

agents/documentation_reviewer.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,10 @@ def _build_analysis_context(self, context: PRContext, prompt_template: str) -> s
7979
def _execute_task(self, agent: Agent, task: Task) -> dict:
8080
"""Execute CrewAI task."""
8181
try:
82-
self._reset_token_tracking()
83-
crew = Crew(
84-
agents=[agent],
85-
tasks=[task],
86-
verbose=False,
87-
callbacks=[self.token_callback]
88-
)
82+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
8983
result = crew.kickoff()
9084
output = str(result) if result else ""
91-
tokens = self._get_token_count()
92-
if tokens == 0 and hasattr(result, 'usage_metadata'):
93-
if hasattr(result.usage_metadata, 'total_tokens'):
94-
tokens = result.usage_metadata.total_tokens
85+
tokens = self._extract_tokens(result)
9586
return {"reasoning": output[:500], "tokens": tokens, "raw_output": output}
9687
except Exception as e:
9788
logger.error(f"Error executing CrewAI task: {e}")

agents/logic_reviewer.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,10 @@ def _build_analysis_context(self, context: PRContext, prompt_template: str) -> s
7676
def _execute_task(self, agent: Agent, task: Task) -> dict:
7777
"""Execute CrewAI task and extract results."""
7878
try:
79-
self._reset_token_tracking()
80-
crew = Crew(
81-
agents=[agent],
82-
tasks=[task],
83-
verbose=False,
84-
callbacks=[self.token_callback]
85-
)
79+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
8680
result = crew.kickoff()
8781
output = str(result) if result else ""
88-
tokens = self._get_token_count()
89-
if tokens == 0 and hasattr(result, 'usage_metadata'):
90-
if hasattr(result.usage_metadata, 'total_tokens'):
91-
tokens = result.usage_metadata.total_tokens
82+
tokens = self._extract_tokens(result)
9283
return {
9384
"reasoning": output[:500] or "Analysis completed",
9485
"tokens": tokens,

agents/performance_reviewer.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,10 @@ def _build_analysis_context(self, context: PRContext, prompt_template: str) -> s
164164
def _execute_task(self, agent: Agent, task: Task) -> dict:
165165
"""Execute CrewAI task."""
166166
try:
167-
self._reset_token_tracking()
168-
crew = Crew(
169-
agents=[agent],
170-
tasks=[task],
171-
verbose=False,
172-
callbacks=[self.token_callback]
173-
)
167+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
174168
result = crew.kickoff()
175169
output = str(result) if result else ""
176-
tokens = self._get_token_count()
177-
if tokens == 0 and hasattr(result, 'usage_metadata'):
178-
if hasattr(result.usage_metadata, 'total_tokens'):
179-
tokens = result.usage_metadata.total_tokens
170+
tokens = self._extract_tokens(result)
180171
return {"reasoning": output[:500], "tokens": tokens, "raw_output": output}
181172
except Exception as e:
182173
logger.error(f"Error executing CrewAI task: {e}")

agents/revision_proposer.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,10 @@ def _generate_single_patch(
128128
def _execute_task(self, agent: Agent, task: Task) -> dict:
129129
"""Execute CrewAI task."""
130130
try:
131-
self._reset_token_tracking()
132-
crew = Crew(
133-
agents=[agent],
134-
tasks=[task],
135-
verbose=False,
136-
callbacks=[self.token_callback]
137-
)
131+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
138132
result = crew.kickoff()
139133
output = str(result) if result else ""
140-
tokens = self._get_token_count()
141-
if tokens == 0 and hasattr(result, 'usage_metadata'):
142-
if hasattr(result.usage_metadata, 'total_tokens'):
143-
tokens = result.usage_metadata.total_tokens
134+
tokens = self._extract_tokens(result)
144135
return {"reasoning": output[:500], "tokens": tokens, "raw_output": output}
145136
except Exception as e:
146137
logger.error(f"Error executing CrewAI task: {e}")

agents/test_reviewer.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,10 @@ def _build_analysis_context(self, context: PRContext, prompt_template: str) -> s
210210
def _execute_task(self, agent: Agent, task: Task) -> dict:
211211
"""Execute CrewAI task."""
212212
try:
213-
self._reset_token_tracking()
214-
crew = Crew(
215-
agents=[agent],
216-
tasks=[task],
217-
verbose=False,
218-
callbacks=[self.token_callback]
219-
)
213+
crew = Crew(agents=[agent], tasks=[task], verbose=False)
220214
result = crew.kickoff()
221215
output = str(result) if result else ""
222-
tokens = self._get_token_count()
223-
if tokens == 0 and hasattr(result, 'usage_metadata'):
224-
if hasattr(result.usage_metadata, 'total_tokens'):
225-
tokens = result.usage_metadata.total_tokens
216+
tokens = self._extract_tokens(result)
226217
return {"reasoning": output[:500], "tokens": tokens, "raw_output": output}
227218
except Exception as e:
228219
logger.error(f"Error executing CrewAI task: {e}")

0 commit comments

Comments
 (0)