What does Rius record when my agent calls CrewAI?
Rius records each model call CrewAI makes as a generation span with the model, token counts, cost and time to first token. It also records the run and tool spans you wrap with observe. Crews and tasks produce no spans of their own, so you wrap the entrypoints you want to see. This works for the Python framework; there is no TypeScript integration for CrewAI.
How it works
CrewAI 1.x drives the OpenAI client natively, so the openai integration captures its model calls. When LiteLLM is installed, CrewAI uses it as a fallback, and you use the litellm integration instead. With a global init(), every installed integration turns on automatically. The CrewAI integration page has the full recipe, and the Integrations overview lists the other frameworks.
The crew structure runs inside the framework, where auto-instrumentation cannot see it. You add that shape with observe wrappers on the entrypoint and on tool bodies.
Here is what lands in the trace:
| In CrewAI | In the trace |
|---|---|
Your function around crew.kickoff() | The root AGENT span, from your observe wrapper |
| A model call | An LLM generation with model, tokens, cost and time to first token |
| A tool | A TOOL span, from your observe wrapper on the tool body |
| Crews and tasks | No spans of their own |
On each generation, the model name and token counts are what Rius uses to compute cost. You never send a cost value. Costs are list-price estimates in US dollars. Prompt and response content goes out with the spans unless you set RIUS_CAPTURE_CONTENT to false, which strips it at export.
A run that calls one tool looks like this:
handle-ticket AGENT 8.4s
├─ ChatCompletion LLM 3.9s agent decides to call the tool
├─ execute_tool account_status TOOL 0ms
└─ ChatCompletion LLM 4.3s agent answersSet it up
-
Install the SDK with the OpenAI extra, next to CrewAI. The extra installs the instrumentation, never the library itself.
pip install "glassflow-rius[openai]" crewaiIf LiteLLM is installed and CrewAI uses it, install
pip install "glassflow-rius[litellm]" litellminstead. -
Call
rius.init(), wrap the tool body withobserveusing the TOOL kind, and wrap your entrypoint as the AGENT root:import rius from crewai import Agent, Crew, Task from crewai.tools import tool from rius import SpanKind rius.init(api_key="ri_...", service_name="my-agent") @tool("account_status") @rius.observe(kind=SpanKind.TOOL) def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) support_agent = Agent( role="Support specialist", goal="Answer support tickets accurately and concisely.", backstory="You resolve product and account questions.", tools=[account_status], llm="gpt-5-mini", ) @rius.observe(name="handle-ticket", kind=SpanKind.AGENT) def handle_ticket(ticket: str) -> str: task = Task( description=f"Resolve this support ticket: {ticket}", expected_output="A short, direct answer.", agent=support_agent, ) return str(Crew(agents=[support_agent], tasks=[task]).kickoff()) -
Keep the decorator order on tools:
@tooloutermost,observeon the function body. The tool span then parents under the run through ambient context. -
Where no terminal is attached, such as CI or containers, set
CREWAI_TRACING_ENABLED=false. CrewAI has its own tracing product and asks about it interactively on the first run. -
To group one conversation or job, scope each kickoff with
rius.session()and reuse your own id. -
Run one ticket that calls a tool, then open the trace in the console. You should see the root
AGENTspan, theLLMgenerations with model, tokens and cost, and your namedTOOLspans.
What you get
- A generation span for each model call, with model, token counts, cost and time to first token.
- A root
AGENTspan named after your entrypoint, so each run is one trace. - A
TOOLspan for each tool body you wrapped, with its duration, placed under the run. - Sessions that group the kickoffs of one conversation or job, when you scope them with
rius.session().
Known limits, verified against crewai 1.15.18:
- Crew and task structure produces no spans. You cannot tell from the trace which task a generation belongs to. Wrap per-task entrypoints with
observeif you need that. - Generation spans carry a generic name,
ChatCompletion. - Streamed calls carry token counts, and so cost, only when usage is enabled on the underlying OpenAI client. See the
include_usagenote on the OpenAI integration page.
When something else fits
- If you need task-level spans without wrapping each task yourself, wrap per-task entrypoints with
observe, since CrewAI’s crews and tasks emit no spans. - If your agent is TypeScript, there is no CrewAI integration. Trace those calls with manual generations or another listed integration.