447 lines
15 KiB
Plaintext
447 lines
15 KiB
Plaintext
---
|
|
id: crewai
|
|
title: CrewAI
|
|
sidebar_label: CrewAI
|
|
---
|
|
|
|
<IntegrationTagsDisplayer native={true} cicdEvals={true} traceability={true} />
|
|
|
|
[CrewAI](https://www.crewai.com/) is a Python framework for orchestrating role-playing autonomous agents that collaborate on multi-step tasks.
|
|
|
|
The `deepeval` integration registers a CrewAI event listener and ships drop-in `Crew`, `Agent`, `LLM`, and `tool` shims that accept metrics. Every `crew.kickoff(...)`, agent execution, LLM call, and tool call becomes a span you can inspect — without rewriting your crew.
|
|
|
|
<AgentTraceTerminal
|
|
title="crewai_agent · deepeval"
|
|
ariaLabel="Example CrewAI trace with per-step metric scores"
|
|
lines={[
|
|
{ kind: "cmd", name: "deepeval test run test_crewai_agent.py" },
|
|
{ kind: "blank" },
|
|
{ kind: "root", prefix: "●", name: "test_crewai_agent" },
|
|
{ kind: "blank", prefix: "│" },
|
|
{
|
|
kind: "agent",
|
|
prefix: "└─",
|
|
name: "weather_reporter",
|
|
metric: "Task Completion",
|
|
score: "0.95",
|
|
duration: "240ms",
|
|
pass: true,
|
|
},
|
|
{
|
|
kind: "llm",
|
|
prefix: " ├─",
|
|
name: "gpt-4o · plan",
|
|
metric: "G-Eval",
|
|
score: "0.43",
|
|
duration: "82ms",
|
|
pass: false,
|
|
},
|
|
{
|
|
kind: "tool",
|
|
prefix: " ├─",
|
|
name: 'get_weather(city="Paris")',
|
|
duration: "44ms",
|
|
},
|
|
{
|
|
kind: "llm",
|
|
prefix: " └─",
|
|
name: "gpt-4o · summarize",
|
|
metric: "Faithfulness",
|
|
score: "0.94",
|
|
duration: "78ms",
|
|
pass: true,
|
|
},
|
|
{ kind: "blank" },
|
|
{
|
|
kind: "summary",
|
|
name: "Trace score 0.77 · 2/3 metrics passed",
|
|
pass: false,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
`deepeval`'s CrewAI integration enables you to:
|
|
|
|
- **Trace every `crew.kickoff(...)`** — each kickoff produces a trace, and each agent execution, LLM call, and tool call becomes a component span.
|
|
- **Attach metrics directly to `Crew`, `Agent`, `LLM`, and `@tool`** through deepeval-aware shims.
|
|
- **Run evals from scripts or CI/CD** — same crew, different surfaces.
|
|
- **Compose with `@observe` and `with trace(...)`** to evaluate larger flows that wrap one or more crew kickoffs.
|
|
|
|
## Getting Started
|
|
|
|
<Steps>
|
|
|
|
<Step>
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
pip install -U deepeval crewai
|
|
```
|
|
|
|
The integration calls `instrument_crewai()` once to register the event listener. After that, the deepeval-aware `Crew`, `Agent`, `LLM`, and `tool` shims accept metrics directly.
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
### Instrument and evaluate
|
|
|
|
Call `instrument_crewai()` at startup, then build the crew with `deepeval.integrations.crewai.Crew`/`Agent` and the `@tool` decorator. Pass metrics on the `Agent` (or `Crew`) you want to evaluate.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from crewai import Task
|
|
from deepeval.integrations.crewai import instrument_crewai, Crew, Agent, tool
|
|
from deepeval.dataset import EvaluationDataset, Golden
|
|
from deepeval.metrics import TaskCompletionMetric
|
|
|
|
instrument_crewai()
|
|
|
|
@tool
|
|
def get_weather(city: str) -> str:
|
|
"""Fetch weather data for a given city."""
|
|
return f"It's always sunny in {city}!"
|
|
|
|
reporter = Agent(
|
|
role="Weather Reporter",
|
|
goal="Provide accurate weather information.",
|
|
backstory="An experienced meteorologist.",
|
|
tools=[get_weather],
|
|
metrics=[TaskCompletionMetric()],
|
|
)
|
|
|
|
task = Task(
|
|
description="Get the current weather for {city} and summarize it.",
|
|
expected_output="A clear weather report for the requested city.",
|
|
agent=reporter,
|
|
)
|
|
|
|
crew = Crew(agents=[reporter], tasks=[task])
|
|
|
|
# Goldens are the inputs you want to evaluate.
|
|
dataset = EvaluationDataset(goldens=[Golden(input="Paris")])
|
|
|
|
for golden in dataset.evals_iterator():
|
|
crew.kickoff({"city": golden.input})
|
|
```
|
|
|
|
Done ✅. You've run your first eval with full traceability into CrewAI via `deepeval`.
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## What gets traced
|
|
|
|
Each `crew.kickoff(...)` call produces a **trace** — the end-to-end unit your user observes. Inside that trace are **component spans** for every step the crew took:
|
|
|
|
- **Agent spans** — one per `Agent` execution within the crew.
|
|
- **LLM spans** — model calls dispatched by agents.
|
|
- **Tool spans** — tool invocations including knowledge retrieval.
|
|
|
|
```text
|
|
Trace ← what the user observes
|
|
└── Agent: weather_reporter ← one crew.kickoff(...) execution
|
|
├── LLM: gpt-4o ← component span: model decides
|
|
├── Tool: get_weather ← component span: tool input + output
|
|
└── LLM: gpt-4o ← component span: final summary
|
|
```
|
|
|
|
The trace and its component spans are independently evaluable.
|
|
|
|
## Running evals
|
|
|
|
There are two surfaces for running evals against a CrewAI crew. Pick by where you want results to surface — your terminal during development, or your CI pipeline as a pass/fail gate.
|
|
|
|
### In CI/CD (pytest)
|
|
|
|
Use the `deepeval` pytest integration. Each parametrized test invocation becomes one `crew.kickoff(...)`; failing metrics fail the test, which fails the build.
|
|
|
|
```python title="test_crewai_agent.py" showLineNumbers
|
|
import pytest
|
|
from crewai import Task
|
|
from deepeval import assert_test
|
|
from deepeval.integrations.crewai import instrument_crewai, Crew, Agent, tool
|
|
from deepeval.dataset import EvaluationDataset, Golden
|
|
from deepeval.metrics import TaskCompletionMetric
|
|
|
|
instrument_crewai()
|
|
|
|
@tool
|
|
def get_weather(city: str) -> str:
|
|
"""Fetch weather data for a given city."""
|
|
return f"It's always sunny in {city}!"
|
|
|
|
reporter = Agent(
|
|
role="Weather Reporter",
|
|
goal="Provide accurate weather information.",
|
|
backstory="An experienced meteorologist.",
|
|
tools=[get_weather],
|
|
)
|
|
task = Task(
|
|
description="Get the current weather for {city} and summarize it.",
|
|
expected_output="A clear weather report for the requested city.",
|
|
agent=reporter,
|
|
)
|
|
crew = Crew(agents=[reporter], tasks=[task])
|
|
|
|
dataset = EvaluationDataset(goldens=[Golden(input="Paris"), Golden(input="London")])
|
|
|
|
@pytest.mark.parametrize("golden", dataset.goldens)
|
|
def test_crewai_agent(golden: Golden):
|
|
crew.kickoff({"city": golden.input})
|
|
assert_test(golden=golden, metrics=[TaskCompletionMetric()])
|
|
```
|
|
|
|
Run it with:
|
|
|
|
```bash
|
|
deepeval test run test_crewai_agent.py
|
|
```
|
|
|
|
### In a script
|
|
|
|
Use `EvaluationDataset` + `evals_iterator(...)`. Each `Golden` becomes one kickoff; metrics score the resulting trace.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
import asyncio
|
|
|
|
from deepeval.dataset import EvaluationDataset, Golden
|
|
from deepeval.evaluate.configs import AsyncConfig
|
|
from deepeval.metrics import TaskCompletionMetric
|
|
...
|
|
|
|
dataset = EvaluationDataset(goldens=[Golden(input="Paris"), Golden(input="London")])
|
|
|
|
async def run_crew(city: str):
|
|
return await crew.kickoff_async({"city": city})
|
|
|
|
for golden in dataset.evals_iterator(
|
|
async_config=AsyncConfig(run_async=True),
|
|
metrics=[TaskCompletionMetric()],
|
|
):
|
|
task = asyncio.create_task(run_crew(golden.input))
|
|
dataset.evaluate(task)
|
|
```
|
|
|
|
Sync (`crew.kickoff`) and async (`crew.kickoff_async`) execution both work; pick whichever matches your code.
|
|
|
|
## Applying metrics to components
|
|
|
|
The `metrics=[...]` you pass to `evals_iterator` evaluates the **trace**. To evaluate a **component** — a specific agent, LLM call, or tool — attach metrics directly where the component is defined.
|
|
|
|
### Agent spans
|
|
|
|
Pass `metrics=[...]` to `deepeval.integrations.crewai.Agent`. The metric is applied to that agent's span on every execution.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.integrations.crewai import Agent
|
|
from deepeval.metrics import TaskCompletionMetric
|
|
...
|
|
|
|
reporter = Agent(
|
|
role="Weather Reporter",
|
|
goal="Provide accurate weather information.",
|
|
backstory="An experienced meteorologist.",
|
|
tools=[get_weather],
|
|
metrics=[TaskCompletionMetric()],
|
|
)
|
|
```
|
|
|
|
### LLM calls
|
|
|
|
Pass `metrics=[...]` to `deepeval.integrations.crewai.LLM`. The metric is applied to LLM spans produced by that model.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.integrations.crewai import LLM, Agent
|
|
from deepeval.metrics import AnswerRelevancyMetric
|
|
...
|
|
|
|
llm = LLM(model="gpt-4o", metrics=[AnswerRelevancyMetric()])
|
|
reporter = Agent(
|
|
role="Weather Reporter",
|
|
goal="Provide accurate weather information.",
|
|
backstory="An experienced meteorologist.",
|
|
tools=[get_weather],
|
|
llm=llm,
|
|
)
|
|
```
|
|
|
|
### Tool calls
|
|
|
|
Pass `metric=[...]` to the deepeval-aware `@tool` decorator. The metric is applied to that tool's span on every call.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.integrations.crewai import tool
|
|
from deepeval.metrics import GEval
|
|
from deepeval.test_case import LLMTestCaseParams
|
|
|
|
@tool(metric=[GEval(
|
|
name="Helpful Weather Lookup",
|
|
criteria="The output must be a clear weather summary for the requested city.",
|
|
evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT],
|
|
)])
|
|
def get_weather(city: str) -> str:
|
|
"""Fetch weather data for a given city."""
|
|
return f"It's always sunny in {city}!"
|
|
```
|
|
|
|
For deterministic tool calls, prefer `update_current_span(...)` to add metadata, inputs, and outputs instead of attaching metrics to the tool span.
|
|
|
|
## Customizing trace and span data
|
|
|
|
The integration captures inputs, outputs, model names, and tool calls automatically. For anything dynamic, the right API depends on where your code runs.
|
|
|
|
- Use `with trace(...)` for trace-level fields (`name`, `tags`, `metadata`, `thread_id`, `user_id`, `metrics`).
|
|
- Use shim kwargs (`Agent(metrics=...)`, `LLM(metrics=...)`, `@tool(metric=...)`) for component-level defaults.
|
|
- Use `update_current_trace(...)` and `update_current_span(...)` from inside a tool body to mutate fields the framework can't see.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.integrations.crewai import tool
|
|
from deepeval.tracing import update_current_trace, update_current_span
|
|
|
|
@tool
|
|
def get_weather(city: str) -> str:
|
|
"""Fetch weather data for a given city."""
|
|
update_current_trace(metadata={"city": city})
|
|
update_current_span(metadata={"source": "static-table"})
|
|
return f"It's always sunny in {city}!"
|
|
```
|
|
|
|
## Advanced patterns
|
|
|
|
The primitives above — `instrument_crewai`, `Crew`, `Agent`, `LLM`, `@tool`, `with trace(...)` — compose around one boundary: CrewAI owns the kickoff lifecycle, and your code attaches metrics where they make sense.
|
|
|
|
### Trace-level metrics with `with trace(...)`
|
|
|
|
When you want a metric on the whole crew run rather than a specific component, wrap the kickoff in `with trace(metrics=[...])`. The metric scores the trace's overall input/output.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.tracing import trace
|
|
from deepeval.metrics import AnswerRelevancyMetric
|
|
...
|
|
|
|
for golden in dataset.evals_iterator():
|
|
with trace(metrics=[AnswerRelevancyMetric()]):
|
|
crew.kickoff({"city": golden.input})
|
|
```
|
|
|
|
#### No trace-level metrics required
|
|
|
|
Trace-level metrics are end-to-end metrics: they score the whole trace. They are not strictly necessary when component metrics are already attached to the agent, LLM, or tool — CI/CD and scripts only need to run the crew.
|
|
|
|
This is how you'd run it:
|
|
|
|
<Tabs items={["CI/CD", "Scripts"]}>
|
|
<Tab value="CI/CD">
|
|
|
|
```python title="test_crewai_agent.py" showLineNumbers
|
|
import pytest
|
|
from deepeval import assert_test
|
|
...
|
|
|
|
@pytest.mark.parametrize("golden", dataset.goldens)
|
|
def test_component_metrics(golden: Golden):
|
|
crew.kickoff({"city": golden.input})
|
|
assert_test(golden=golden)
|
|
```
|
|
|
|
```bash
|
|
deepeval test run test_crewai_agent.py
|
|
```
|
|
|
|
</Tab>
|
|
<Tab value="Scripts">
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
...
|
|
|
|
for golden in dataset.evals_iterator():
|
|
crew.kickoff({"city": golden.input})
|
|
```
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
### Wrap a kickoff in `@observe`
|
|
|
|
When the crew run is part of a larger operation, decorate the outer function with `@observe`. CrewAI spans nest under your observed span automatically.
|
|
|
|
```python title="crewai_agent.py" showLineNumbers
|
|
from deepeval.tracing import observe
|
|
...
|
|
|
|
@observe(name="respond_to_user")
|
|
def respond_to_user(city: str) -> str:
|
|
result = crew.kickoff({"city": city})
|
|
return str(result)
|
|
```
|
|
|
|
## API reference
|
|
|
|
The deepeval-aware shims accept the framework's standard kwargs plus the following:
|
|
|
|
| Shim | Kwarg | Description |
|
|
| ------------- | --------- | -------------------------------------------------------------------- |
|
|
| `Crew(...)` | `metrics` | Metrics applied to the crew's top-level span on every kickoff. |
|
|
| `Agent(...)` | `metrics` | Metrics applied to this agent's span on every execution. |
|
|
| `LLM(...)` | `metrics` | Metrics applied to LLM spans produced by this model. |
|
|
| `@tool(...)` | `metric` | Metrics applied to this tool's span on every call. |
|
|
|
|
For runtime helpers (`update_current_trace`, `update_current_span`) and the test-decorator surface (`@observe`, `@assert_test`, `with trace(...)`), see the [tracing reference](/docs/evaluation-llm-tracing).
|
|
|
|
## FAQs
|
|
|
|
<FAQs
|
|
qas={[
|
|
{
|
|
question:
|
|
"My crew has several agents — can I evaluate each one individually?",
|
|
answer: (
|
|
<>
|
|
Yes. Attach <code>metrics=[...]</code> directly to the specific{" "}
|
|
<code>Agent(...)</code> shim and it scores that agent's span on every
|
|
execution, independent of the rest of the crew. You can do the same on{" "}
|
|
<code>LLM(...)</code> and <code>@tool</code> for finer-grained scoring.
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
question: "Can I gate CI/CD on a CrewAI crew's metrics?",
|
|
answer: (
|
|
<>
|
|
Yes. With component metrics already on your <code>Agent</code> /{" "}
|
|
<code>LLM</code> / <code>@tool</code> shims, just{" "}
|
|
<code>crew.kickoff(...)</code> inside a parametrized{" "}
|
|
<code>pytest</code> test and call <code>assert_test(golden=golden)</code>{" "}
|
|
under <code>deepeval test run</code>.
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
question: "Can I review crew runs on the cloud?",
|
|
answer: (
|
|
<>
|
|
Yes, optionally. After <code>deepeval login</code>,{" "}
|
|
<a href="https://www.confident-ai.com">Confident AI</a> renders each
|
|
kickoff as a trace — every agent, LLM, and tool span with its score —
|
|
in a shared UI.
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
question: "Can I monitor a CrewAI app in production?",
|
|
answer: (
|
|
<>
|
|
Yes. <code>instrument_crewai()</code> keeps emitting spans in
|
|
production, and when logged into Confident AI those live traces feed{" "}
|
|
<a href="https://www.confident-ai.com/docs/llm-tracing/online-evals">
|
|
online evals
|
|
</a>{" "}
|
|
on real traffic.
|
|
</>
|
|
),
|
|
},
|
|
]}
|
|
/>
|