Trace and evaluate a CrewAI research crew
Instrument a CrewAI multi-agent research crew with traceAI and read completeness, groundedness, and context relevance scores off its spans in Observe.
Build a four-agent CrewAI research crew (market researcher, competitive analyst, report writer, quality analyst), auto-instrument it with traceAI, and attach a platform Eval Task so completeness, groundedness, and context relevance scores land on every span in Observe.
| Time | Difficulty | Package |
|---|---|---|
| 25 min | Intermediate | traceai-crewai |
- Future AGI account → app.futureagi.com
- API keys:
FI_API_KEYandFI_SECRET_KEY(see Get your API keys) - OpenAI API key
- SerperDev API key for web search (serper.dev)
- Python 3.11+
Install
pip install crewai crewai_tools traceai-crewai fi-instrumentation-otel openai
export OPENAI_API_KEY="your-openai-api-key"
export FI_API_KEY="your-futureagi-api-key"
export FI_SECRET_KEY="your-futureagi-secret-key"
export SERPER_API_KEY="your-serper-api-key"
Tutorial
Register the trace provider and instrument CrewAI
CrewAIInstrumentor wraps CrewAI’s internal execution methods so every agent run, tool call, and task produces a span automatically. Because this project is registered as ProjectType.OBSERVE, you configure evals as a platform Eval Task attached to those spans later, rather than calling an evaluator from code.
from crewai import LLM, Agent, Crew, Process, Task
from crewai_tools import SerperDevTool, FileReadTool
from fi_instrumentation import register, FITracer
from fi_instrumentation.fi_types import ProjectType
from traceai_crewai import CrewAIInstrumentor
trace_provider = register(
project_type=ProjectType.OBSERVE,
project_name="crewai-research-team",
set_global_tracer_provider=True,
)
# Auto-instruments crewai.Task._execute_core and crewai.Crew.kickoff.
# No manual spans needed for the crew's own execution.
CrewAIInstrumentor().instrument(tracer_provider=trace_provider)
tracer = FITracer(trace_provider.get_tracer(__name__))You should see no output here beyond a clean exit. The instrumentor is now active for every CrewAI call in the process.
Define the research team agents
Four specialized agents, each with a narrow role. allow_delegation=False keeps the sequential process predictable: each agent runs once, in order.
llm = LLM(model="gpt-4o", temperature=0.7, max_tokens=2000)
market_researcher = Agent(
role="Senior Market Research Analyst",
goal="Research and analyze emerging technology trends and market dynamics",
backstory=(
"You are a market research analyst with 15 years in technology markets. "
"You favor data-backed claims over speculation."
),
llm=llm,
tools=[SerperDevTool()],
allow_delegation=False,
)
competitive_analyst = Agent(
role="Competitive Intelligence Specialist",
goal="Analyze competitive landscapes and identify market opportunities",
backstory=(
"You analyze competitor strategies and market positioning to find gaps "
"a new entrant could exploit."
),
llm=llm,
tools=[SerperDevTool()],
allow_delegation=False,
)
report_writer = Agent(
role="Technical Report Writer",
goal="Create a comprehensive, well-structured research report",
backstory="You turn raw research into executive summaries and recommendations.",
llm=llm,
tools=[FileReadTool()],
allow_delegation=False,
)
quality_analyst = Agent(
role="Research Quality Assurance Specialist",
goal="Verify accuracy and completeness of the research findings",
backstory="You fact-check claims and flag logical gaps before a report ships.",
llm=llm,
allow_delegation=False,
)You should see the four Agent objects construct without error. Nothing runs yet.
Build the crew's tasks
Each task names its agent and its expected output. report_generation_task and quality_assurance_task don’t yet declare context=, so CrewAI has no explicit link telling them which upstream task output to build on. The next step shows why that matters.
def create_research_tasks(research_topic: str) -> list[Task]:
market_research_task = Task(
description=(
f"Research the market for: {research_topic}. Cover market size, "
"growth drivers, major players, and regulatory landscape. Cite sources."
),
agent=market_researcher,
expected_output="A market research summary with cited data points",
)
competitive_analysis_task = Task(
description=(
f"Analyze the competitive landscape for: {research_topic}. "
"Cover top competitors, positioning, and market gaps."
),
agent=competitive_analyst,
expected_output="A competitive analysis with named competitors",
)
report_generation_task = Task(
description=(
f"Write a research report on: {research_topic} with an executive "
"summary, market overview, competitive landscape, and recommendations."
),
agent=report_writer,
expected_output="A structured research report",
)
quality_assurance_task = Task(
description=(
"Review the report for factual accuracy, logical consistency, and "
"completeness. List any issues found."
),
agent=quality_analyst,
expected_output="A quality review with a pass/fail verdict",
)
return [
market_research_task,
competitive_analysis_task,
report_generation_task,
quality_assurance_task,
]You should see a list of four Task objects. Process.sequential in the next step runs them in this order.
Run the crew, then fix a weak groundedness score
def run_research_crew(research_topic: str) -> str:
tasks = create_research_tasks(research_topic)
research_crew = Crew(
agents=[market_researcher, competitive_analyst, report_writer, quality_analyst],
tasks=tasks,
process=Process.sequential,
memory=True,
)
result = research_crew.kickoff()
return str(result)
if __name__ == "__main__":
topic = "Generative AI in Healthcare: Market Opportunities and Challenges"
report = run_research_crew(topic)
print(report[:500])Output shape (illustrative):
## Executive Summary
Generative AI is reshaping healthcare diagnostics and...You should see the crew’s four agents run in sequence in your terminal (CrewAI’s own verbose logging), followed by the printed report excerpt. Once the Eval Task from the next step is attached, open the report_generation_task span in Observe: without an explicit link to the upstream research, report_writer only sees its own task description as input, so groundedness scores low. There’s nothing in that span’s captured context for the eval to check the report against.
Fix it by wiring the dependency explicitly, so CrewAI feeds the upstream outputs into the report task and quality-assurance task instead of leaving them to infer it:
report_generation_task = Task(
description=(
f"Write a research report on: {research_topic} with an executive "
"summary, market overview, competitive landscape, and recommendations."
),
agent=report_writer,
expected_output="A structured research report",
context=[market_research_task, competitive_analysis_task],
)
quality_assurance_task = Task(
description=(
"Review the report for factual accuracy, logical consistency, and "
"completeness. List any issues found."
),
agent=quality_analyst,
expected_output="A quality review with a pass/fail verdict",
context=[market_research_task, competitive_analysis_task, report_generation_task],
)Rerun the same topic. The report_generation_task span now carries the market and competitive findings as input, and the groundedness score on that span moves up from the first run: the report has actual source material to be grounded against instead of just the topic string.
Configure a platform Eval Task for completeness, groundedness, and context relevance
In the dashboard, define evals as an Eval Task and attach them to the crew’s span types rather than calling an evaluator from code. This scores every run as it’s generated, including the two you just made.
Open app.futureagi.com → Observe → the crewai-research-team project → Eval Task, and attach Completeness, Groundedness, and Context Relevance to the task span type. Each of the three resolves on the built-in evals reference.
You should see the new Eval Task listed against the span type you selected, and it starts scoring the next trace that hits that span.
Inspect the trace and eval scores in Observe
Open the crewai-research-team project. Each run appears as a trace with one span per agent task.

The trace view shows the four agent tasks in execution order, with tool calls nested under the market researcher and competitive analyst spans
Open the report_generation_task span to see the three eval scores attached to it directly by the Eval Task.

Each eval score sits on the span it was computed from, so you can correlate a low score with the exact agent output that produced it
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No spans appear in Observe | CrewAIInstrumentor().instrument() was never called, or was called after the crew already ran | Call instrument() immediately after register(), before constructing any Agent or Crew |
TypeError: unexpected keyword argument 'debug' from register() | register() has no debug parameter | Use verbose=True (it’s the default) to increase log output instead |
| Evals never run | No Eval Task is attached to the span type yet, or it’s attached to a project that isn’t ProjectType.OBSERVE | Attach an Eval Task to the relevant span type in the project’s Observe settings, and confirm the project was registered with project_type=ProjectType.OBSERVE |
crewai_tools import fails on SerperDevTool | SERPER_API_KEY isn’t set, or crewai_tools version mismatch with crewai | Set SERPER_API_KEY before constructing the tool, and pin crewai and crewai_tools to compatible versions |
| Agents produce inconsistent or incomplete task output | Task description is vague, or the agent has no tool for the information it needs | Make task descriptions explicit about required sections, and equip agents only with the tools their role needs |
Crew.kickoff() hangs or times out on a task | A tool call (typically the web search tool) is stalling on a rate limit or network issue | Check the SerperDev dashboard for rate limits, and add a task-level timeout if your CrewAI version supports it |
| Groundedness scores are consistently low on a downstream task’s span | The task has no context=[...] list, so CrewAI never feeds it the upstream tasks’ output and the span only captures the task’s own description | Add the upstream Task objects to context=[...] on the downstream task, as shown in the “Run the crew” step |
Next: Attach inline evals to production traces to see the same pattern applied outside a multi-agent framework.
Questions & Discussion