CrewAI
Trace crews built with CrewAI with Rius: the
model calls the framework makes arrive as generations with tokens and cost,
and observe wrappers give the run and its tools their shape. This guide
covers the Python framework; the runnable example behind it lives in
agent-observability-examples .
How coverage works
CrewAI 1.x drives the OpenAI client natively (it is a hard dependency), so
the openai integration captures its
model calls. LiteLLM is an optional fallback the framework uses only when
it is installed; in that setup, use the
litellm integration instead.
As with other frameworks covered through a client-library integration, the
crew structure runs inside the framework where auto-instrumentation cannot
see it: wrap the entrypoint and the tool bodies with
observe.
CrewAI also ships its own tracing product and asks about it interactively
on first run; set CREWAI_TRACING_ENABLED=false to keep it off
non-interactively (CI, containers).
Setup
pip install "glassflow-rius[openai]" crewaiimport rius
from crewai import Agent, Crew, Task
from crewai.tools import tool
from rius import SpanKind
rius.init(api_key="gf_...", 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())The decorator order on tools matters: @tool outermost, observe on the
function body. The TOOL span parents under the run through ambient context.
What lands where
| In the framework | In the trace |
|---|---|
Your entrypoint around crew.kickoff() | The root AGENT span (your observe wrapper) |
| A model call | An LLM generation with model, tokens, cost, TTFT |
| A tool | A TOOL span (your observe wrapper on the body) |
| Crews and tasks | No spans of their own; see blind spots |
A run that calls a tool looks like this (real trace from the example,
crewai 1.15.18):
handle-ticket AGENT 8.4s
├─ ChatCompletion LLM 3.9s agent decides to call the tool
├─ search_knowledge_base TOOL 0ms
└─ ChatCompletion LLM 4.3s agent answersBlind spots
Verified against crewai 1.15.18:
- Crew and task structure produces no spans. Which task a generation
belongs to is not visible from the trace; wrap per-task entrypoints with
observeif that matters to you. - Generation span names are generic (
ChatCompletion). - First run prompts interactively about CrewAI’s own tracing; set
CREWAI_TRACING_ENABLED=falsewhere no terminal is attached.
Sessions
One conversation or job is one session: scope each kickoff with
rius.session(), reusing your own id.
Verify
Run one ticket that calls a tool, then open the trace in the console: the
root AGENT span, the LLM generations with model, tokens, and cost, and
your named TOOL spans should all be there.
Streamed calls need usage enabled on the underlying client to carry token
counts; see the OpenAI integration
for the include_usage note.