Skip to main content

Trace a CrewAI Trip-Planning Crew

What you'll build: a two-agent CrewAI crew — a Researcher who searches the web and a Planner who turns that research into an itinerary — run twice in the same conversation: once to plan a trip, once to revise it. Every agent, tool, and model call lands in AcruxCore automatically, with no AcruxCore code anywhere in the crew.

Every tutorial so far on this site builds the agent loop yourself, on top of AcruxCore's gateway or SDK. CrewAI is different: it's a framework with its own agents, its own tool-calling loop, and its own way of instrumenting itself for observability — the OpenTelemetry (OTel) wire protocol, the same one nearly every agent framework speaks. AcruxCore's POST /api/v1/traces/otlp endpoint accepts that protocol directly, so pointing an already-working CrewAI app at AcruxCore is an environment-variable change, not a rewrite.

Prefer a notebook?

trip_planner.ipynb is this whole page as one runnable notebook, written for a first-timer: a preflight cell that prints every instrumentor version, the crew built step by step, a live read of both traces and the real Tavily queries inside them, and four real ways to get OTLP wiring wrong — including the two that produce a trace with no tokens and no error. It renders on GitHub with its saved output, so you can read it through before running anything.

1. Create an AcruxCore API key

Open Account & keys → New key, name it crewai-tutorial, and create it.

Copy the key the moment it's shown — this is the only time the full value appears.

2. Install the SDK's OTel helper

CrewAI doesn't know about AcruxCore, and it never will — the wiring lives entirely outside the crew, in acruxcore.otel.register(), a small convenience helper the Python SDK ships for exactly this: pointing a standard OpenTelemetry pipeline at AcruxCore's OTLP endpoint.

export ACRUXCORE_API_KEY=<your-acruxcore-key>
export ACRUXCORE_BASE_URL=https://api.acruxcore.com/api/v1

register() reads those two variables and does nothing you couldn't do by hand — it builds the same TracerProvider + BatchSpanProcessor + OTLPSpanExporter every OTel pipeline needs, pointed at $ACRUXCORE_BASE_URL/traces/otlp. If you'd rather not add the SDK as a dependency, the OTLP API reference shows the same four lines written out by hand — both reach the identical endpoint.

3. Build the crew

Two agents, a shared model, and one line of code that decides how they hand work to each other — context=[research_task] tells the Planner's task to read the Researcher's output:

researcher = Agent(
role="Destination Researcher",
goal="Find concrete, well-reviewed attractions, restaurants, and neighborhoods for a trip",
tools=[TavilySearchTool(max_results=5)],
llm="gpt-4o-mini",
)

planner = Agent(
role="Itinerary Planner",
goal="Turn research into a clear, day-by-day itinerary",
llm="gpt-4o-mini",
)

plan_task = Task(..., agent=planner, context=[research_task])
crew = Crew(agents=[researcher, planner], tasks=[research_task, plan_task], process=Process.sequential)

The only AcruxCore-aware code in the whole file is one call, above the crew definition — acruxcore.otel.register() reads the two env vars from Step 2 and turns on both instrumentors this crew needs:

from acruxcore.otel import register

tracer_provider = register(
service_name="crewai-trip-planner",
instrument=["crewai", "openai"],
)
Two instrumentors, not one

"crewai" traces the crew's own orchestration — which agent ran, which task, which tool. It does not see token counts or cost, because CrewAI calls the model through the plain openai Python SDK by default (the litellm integration is an opt-in crewai[litellm] extra most installs don't have). "openai", from the same OpenInference project, captures that layer — list both, or your LLM spans will be missing model, tokens, and cost.

The full script also threads a session.id across both crew runs with openinference.instrumentation.using_session(session_id), so AcruxCore groups them as one conversation — see trip_planner.py on GitHub for the complete source.

4. Run it

pip install crewai crewai-tools tavily-python 'acruxcore[otel]' \
openinference-instrumentation-crewai openinference-instrumentation-openai
python trip_planner.py

The second call passes the first call's real output back in as prior_itinerary — a genuine revision, not a second unrelated plan:

=== Turn 1 itinerary ===

### Day 1: Historical Architecture & Traditional Cuisine
**Morning:** Start your day at Jerónimos Monastery. Arrive early to explore the intricate
stonework of this UNESCO World Heritage site...
**Dinner:** Experience fine dining at Belcanto, a Michelin-starred restaurant by renowned
chef José Avillez...

### Day 2: Contemporary Architecture & Gastronomic Exploration
**Morning:** Start your day at the impressive Lisbon Oceanarium in the Parque das Nações...
**Afternoon:** Explore LX Factory, a vibrant cultural space filled with shops, restaurants,
and art installations...

=== Turn 2 itinerary (refinement) ===

### Revised 3-Day Lisbon Itinerary

#### Day 2: A Relaxing Day with a Cooking Class

**Morning:**
- Start your day with a leisurely morning at **Cascais**. Take a train to this charming
coastal town and relax at **Praia da Rainha** or stroll through the quaint streets at your
own pace.

**Cooking Class:**
- Enrich your experience by joining a hands-on cooking class at **CookingLisbon**, where you
can learn to prepare traditional dishes such as codfish à Brás or pastel de nata.

session.id used for both turns: crewai-trip-planner-demo

Day 2 of the revision is genuinely different — relaxed pace, a cooking class added — because the Planner agent read turn 1's actual itinerary, not just a text description of the request.

5. See both turns grouped into one session

Open Observability → Sessions and click crewai-trip-planner-demo: both crew runs are there, each its own trace, with real token counts and cost — CrewAI never told AcruxCore these two runs were related; the shared session.id attribute on every span did that.

The crewai-trip-planner-demo session showing two traces, each with 9 spans, real token counts, and real cost

6. Follow the crew's own span tree

Open the first trace. The tree is CrewAI's own execution order, captured without a single line of tracing code in the crew: the Researcher agent calls the model, runs the Tavily searches the model asked for, calls the model again to summarize, then hands off to the Planner agent for a final model call:

Trace tree showing a chain root span Crew.kickoff, an agent span for Destination Researcher containing an LLM span, three Tavily Search tool spans, and a second LLM span, followed by a sibling agent span for Itinerary Planner containing one LLM span

Click a Tavily Search span to see the real query the Researcher chose and the real search results it got back:

The expanded Tavily Search span showing Input with the query &quot;attractions in Lisbon architecture&quot; and Output with real search results from visitportugal.com and other travel sites

Nothing here was reported by hand — openinference-instrumentation-crewai captured the orchestration, openinference-instrumentation-openai captured the model calls, and AcruxCore's OTLP endpoint mapped the OpenInference attributes it received onto the same agent / tool / llm span kinds every other tutorial on this site produces.

What's next