CrewAI
Crew calls are recorded through LiteLLM to the provider client. Where to attach attribution given that the crew owns the execution loop.
CrewAI routes model calls through LiteLLM to the provider client the SDK patches, so crew
spend is recorded with no integration. Both CrewAI and LiteLLM are on the frame-skip list,
so caller_file resolves through both layers to your code.
pip install crewai capsera
The attribution constraint
crew.kickoff() runs the whole crew inside CrewAI's loop. Your code calls one method and
does not regain control until every agent has finished, so there is no per-agent function
of yours to decorate.
There are two options, depending on the question you need answered.
Per crew
Attribute the whole crew as one unit. This is enough for most cases.
from crewai import Agent, Crew, Task
import capsera
capsera.init(api_key=os.environ["CAPSERA_API_KEY"])
def build_crew() -> Crew:
researcher = Agent(
role="Researcher",
goal="Find the relevant facts",
backstory="A careful analyst.",
)
writer = Agent(
role="Writer",
goal="Turn findings into a brief",
backstory="A concise writer.",
)
return Crew(
agents=[researcher, writer],
tasks=[
Task(description="Research {topic}", agent=researcher, expected_output="Notes"),
Task(description="Write a brief", agent=writer, expected_output="A brief"),
],
)
@capsera.agent("content-crew", team="marketing")
def run_campaign(topic: str) -> str:
return build_crew().kickoff(inputs={"topic": topic})
Every call any agent in the crew makes is attributed to content-crew, giving a per-run
total that can be compared across runs.
build_crew is not decorated, because it constructs objects and makes no model calls.
Decorating it would create an agent with no spend.
Per crew agent
To identify which agent inside the crew is expensive, attach attribution where the work happens, which is a tool the agent calls:
from capsera import crewai_agent
@crewai_agent("researcher", team="marketing")
def research_tool(query: str) -> str:
return client.messages.create(...)
crewai_agent and crewai_task are @agent() plus a record of the framework.
CrewAI's internal reasoning calls, the ones it makes to decide what to do next rather than
the ones your tools make, happen inside the crew loop and inherit whatever scope wraps
kickoff(). They cannot be split per agent from outside, because your code is not on the
stack when they happen.
Per-agent attribution therefore works for tool-driven work but not for the crew's own deliberation. If a crew's cost is dominated by deliberation, the per-crew view is the accurate one.
Which provider is recorded
Whatever the model string resolves to. CrewAI's llm="gpt-4o-mini" goes through LiteLLM to
the openai client and records openai. An Anthropic model records anthropic. The SDK
reads the client that ran rather than the configuration string.
Runaway crews
Crews loop, and a crew that fails to converge makes many more calls than intended. Because every call carries the same attribution, the signal is call count rather than cost per call. A crew averaging 12 calls per run that begins averaging 200 is visible in a per-agent view.
A blocking budget stops the crew mid-loop by
raising BudgetExceededError out of the provider call, which CrewAI surfaces as a task
failure.