Skip to Content

How do I see which step was slow in an agent built with CrewAI?

Rius shows which step was slow in a CrewAI run as the duration of each model call and tool call in the trace, once you wrap the entrypoint and the tool bodies with observe. The model calls CrewAI makes are recorded on their own, each with its model, tokens, cost and time to first token. Crews and tasks produce no spans, so a slow task shows up only if you wrap it yourself. You read the timings in the trace waterfall, the Map view or the Spans tab.

How it works

  • Model calls need nothing from you. CrewAI drives the OpenAI client natively, so the openai integration records each model call as an LLM generation with model, tokens, cost, time to first token and its own duration. If LiteLLM is installed, CrewAI uses it instead, and the litellm integration records the calls. Both are covered on the CrewAI integration page.
  • Run structure needs your observe wrappers. The crew runs inside the framework, where auto-instrumentation cannot see it. You wrap the function that calls crew.kickoff() as the root AGENT span and each tool body as a TOOL span.
  • Crews and tasks get no spans of their own. From the trace alone you cannot tell which task a model call belongs to. If you need per-task timing, wrap each per-task entrypoint with observe.
  • Model call spans have generic names. Each one is named ChatCompletion, so you tell them apart by their position in the tree, their duration and their input and output.

With a default init(), installed integrations are turned on automatically. The full list is on the Integrations page.

Set it up

  1. Install the SDK with the OpenAI extra next to CrewAI: pip install "glassflow-rius[openai]" crewai.
  2. Initialize Rius, then wrap the entrypoint as an AGENT span and the tool body as a TOOL span:
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") def lookup(email: str) -> str: return "active" # replace with your own account lookup @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())
  1. Keep the decorator order on tools: @tool outermost, observe on the function body. Give the tool the TOOL kind. A bare observe makes a CHAIN span, and the Tool kind in the Spans tab filter never lists it.
  2. Run one ticket that calls a tool, then open Traces in the console.
  3. Read the timings, as described in Exploring traces:
    • Open the trace for the Waterfall view. It shows the spans as a tree on a timeline, so the longest bar is the slow step.
    • Switch to the Map tab and pick the latency lens. Clicking a node opens the tree filtered to that node’s spans.
    • On the Spans tab, choose a kind in the All kinds menu, such as LLM or Tool, and click the Duration header to sort.
  4. Where no terminal is attached (CI, containers), set CREWAI_TRACING_ENABLED=false. CrewAI otherwise asks interactively about its own tracing product on first run.

What you get

A run that calls one tool reads like this in the tree:

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
  • Where the time went. Each model call and each tool call has its own duration under the root span, so you see whether the wait was the model deciding, the tool running or the model answering.
  • Model detail on each call. Select an LLM span to see its model, tokens, cost and time to first token. The span panel has previous and next buttons, and the keys ]l and [l jump between LLM spans.
  • Sessions. Scope each kickoff with rius.session() so one conversation or job groups together. Searching a session ID on the Spans tab lists every span in it.
  • Cost as an estimate. Cost is a list-price estimate in US dollars, and a call that reported no token usage shows a blank cost, not $0.

For the wider picture of running agents this way, see Monitor AI agents in production.

When something else fits

  • If you need to know which task a slow model call belongs to, add an observe wrapper around each per-task entrypoint, because Rius draws no task spans on its own.
  • If LiteLLM is installed in your CrewAI project, follow the litellm integration for the model calls instead of the openai one.
  • For another framework, the Integrations page lists what each one records on its own and where you add wrappers.

Still stuck? Get help

Last updated on