My CrewAI agent is slow to respond. Why?
Rius shows where a slow CrewAI run spends its time, because the trace of each run has a span for your entrypoint, one for each model call and one for each tool, and each span has its own duration. Open the slow trace and find the longest span. In most runs it is a model call, so check those first, then your tools. To catch it next time, set up a custom alert on duration for the agent and watch the latency percentiles on Home.
How it works
With the openai integration active, Rius records each CrewAI model call as a generation span with model, tokens, cost and time to first token. Your observe wrappers add the root span for the run and a span for each tool. A run that calls one tool reads 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 answersHow to read it:
- Long model-call spans. The model is the slow part. Open the span to see the model, its token counts and, for streamed calls, the time to first token.
- Long tool spans. The tool is the slow part.
- Long root span, short children. The time goes to your own code, or to a tool with no wrapper, since only wrapped tools get a span.
- Growing input tokens. Each model call carries a bigger prompt than the last, so later calls take longer.
Crews and tasks produce no spans of their own, and every model call is named ChatCompletion. To tell which task a slow call belongs to, wrap each task’s entrypoint with observe. The CrewAI integration page covers these blind spots.
Set it up
- Install the OpenAI instrumentation next to CrewAI. The integrations overview covers the full setup.
pip install "glassflow-rius[openai]" crewai- Wrap the entrypoint as an AGENT span and each tool body as a TOOL span, so the slow step shows up by name. Put
@tooloutermost andobserveon the function body.
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())-
Find the slow run on the Traces page. Every row shows latency, and you can sort or filter to the slow ones. Open a trace and start in the waterfall, where each span sits on a timeline. The Map tab has a latency lens that colors the slowest spans, and clicking a node opens its spans in the tree.
-
To compare many runs, use the Spans tab. Pick Tool or LLM in the All kinds menu and click the Duration header to sort. See Exploring traces for the full walkthrough.
-
Use the tools on the trace page. The context growth strip plots input tokens for each model call in order, so a prompt that keeps growing shows up first. Compare runs sets the slow trace beside another run of the same agent and shows where latency moved.
What you get
- A named cause. You see whether the time went to the model, a tool or your own code, from one trace.
- Time to first token. Streamed model calls record it. Home shows p50 and p95 for the workspace, a value for each agent and a p95 for each model, so a slow-to-start model stands out. A workspace whose calls never stream shows a dash.
- Latency trends. Home shows p50, p95 and p99 latency for finished traces over 15 minutes to 30 days, so you can tell whether slowness is one run or the whole period. See Usage and cost.
- Hung runs. With partial spans on (
RIUS_PARTIAL_SPANS), a run still going shows as running…. A trace stuck there long after the work should have ended is a crash, a hang or a killed process. - An alert for next time. Create a custom alert on duration, or on time to first token, and filter by Service or Agent so it measures whole runs. A rule measures only the spans it matches, so a filter on tool spans alone measures only tool time. Webhook, email and Slack channels deliver it.
For the wider picture, see Monitor AI agents in production.
When something else fits
- If you need per-task timing, add
observewrappers around each task’s entrypoint, because CrewAI crews and tasks produce no spans of their own. - If LiteLLM is installed, CrewAI uses it as a fallback. Use the
litellmintegration in that setup, as the CrewAI page explains. - If streamed calls show no token counts, so you cannot check whether a growing prompt is the cause, enable usage on the client. Streamed calls carry token counts only then.