Skip to Content

How do I trace CrewAI in Python?

Rius traces CrewAI in Python with one install command, one init call and an observe wrapper around your crew entrypoint. Every model call the crew makes arrives as a generation with model, tokens and cost. Your entrypoint becomes the root agent span, and tools you wrap become tool spans. You see the whole run as one trace in the console.

How it works

CrewAI 1.x drives the OpenAI client natively, so the Rius openai integration captures its model calls. With a global init(), every installed integration turns on automatically, so you add no instrumentation code for the model calls.

The crew structure runs inside the framework, where auto-instrumentation cannot see it. You give the run its shape with observe wrappers on two things:

  • The entrypoint that calls crew.kickoff(). It becomes the root AGENT span.
  • The body of each tool. It becomes a TOOL span that parents under the run through ambient context.

If LiteLLM is installed, CrewAI uses it as a fallback for its model calls. In that setup, install the litellm extra instead of openai, with pip install "glassflow-rius[litellm]" litellm. The Integrations page lists every integration and which library captures the model calls for each.

CrewAI also ships its own tracing product and asks about it interactively on first run. Set CREWAI_TRACING_ENABLED=false to keep it off where no terminal is attached, such as CI or containers.

Set it up

  1. Install the SDK with the openai extra, plus CrewAI. The extra installs the instrumentation for the OpenAI client, not the client itself. Python 3.10 or newer is required. The installation page covers every option.

    pip install "glassflow-rius[openai]" crewai
  2. Call rius.init() once, as early as possible, before you run the crew. Set service_name explicitly, because it is how you find your agent in trace filters and cost breakdowns.

  3. Wrap the entrypoint with observe and the AGENT kind, and wrap each tool body with observe and the TOOL kind. Put @tool outermost and observe on the function body. This is a minimal traced run:

    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())

    Here lookup stands for your own function that fetches the account.

  4. Run one ticket that calls a tool, for example handle_ticket("Is my account active?"), then open the trace in the console.

  5. If one conversation or job should be one session, scope each kickoff with rius.session() and reuse your own id.

Streamed calls carry token counts only when usage is enabled on the underlying OpenAI client. The OpenAI integration page explains the include_usage setting.

What you get

A run that calls a tool looks like this in the trace:

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 answers

The mapping from CrewAI to the trace is:

In CrewAIIn the trace
Your entrypoint around crew.kickoff()The root AGENT span
A model callAn LLM generation with model, tokens, cost and time to first token
A toolA TOOL span from your observe wrapper
Crews and tasksNo spans of their own

Two things to know, checked against crewai 1.15.18. Crews and tasks produce no spans, so the trace does not show which task a generation belongs to. Wrap per-task entrypoints with observe if you need that. Generation spans are named ChatCompletion, so tell them apart by position in the tree.

Cost on each generation is a list-price estimate in US dollars, computed by Rius from the model and token counts. You never send a cost value. The full CrewAI recipe is on the CrewAI integration page.

When something else fits

  • If you need spans for each crew and task without wrapping them yourself, Rius does not produce them from CrewAI today, so wrap each task entrypoint with observe.
  • If your project is in TypeScript, there is no CrewAI integration in that language. Trace those calls with manual generations, as the Integrations page describes.

Still stuck? Get help

Last updated on