CrewAI has become the fastest-growing multi-agent AI framework in 2026, with over 14,800 monthly searches and a rapidly expanding developer community. If you want to build AI systems where multiple agents collaborate on complex tasks – research, analysis, content creation, coding – CrewAI offers the most intuitive Python framework for the job. This step-by-step tutorial walks you through building a complete multi-agent system from scratch, covering installation, agent creation, tool integration, and production deployment across 13 detailed steps.
Unlike single-prompt AI interactions, CrewAI lets you define specialized agents with distinct roles, goals, and backstories that work together as a crew. Each agent can use different tools, delegate tasks to other agents, and produce structured outputs. Whether you are building an automated research pipeline, a content generation system, or a data analysis workflow, this tutorial gives you every line of code you need. By the end, you will have a fully working multi-agent project that you can extend for your own use cases.
Prerequisites and Environment Setup
Before diving into CrewAI, make sure your development environment meets these requirements. CrewAI requires Python 3.10 or higher (up to Python 3.13) and works on macOS, Linux, and Windows. You will also need an API key from at least one LLM provider – OpenAI, Anthropic, Google Gemini, or Azure OpenAI are all supported through CrewAI’s built-in LiteLLM integration.
| Requirement | Minimum Version | Recommended |
|---|---|---|
| Python | 3.10 | 3.12 or 3.13 |
| pip | 23.0 | 24.0+ |
| Operating System | macOS 12+, Ubuntu 20.04+, Windows 10+ | macOS 14+, Ubuntu 22.04+ |
| RAM | 4 GB | 8 GB+ |
| LLM API Key | OpenAI, Anthropic, or Gemini | OpenAI GPT-4o or Claude Sonnet |
| Disk Space | 500 MB | 1 GB+ (for dependencies) |
CrewAI supports multiple LLM providers out of the box through its LiteLLM integration. You can use OpenAI models (GPT-4o, GPT-4o-mini), Anthropic models (Claude Sonnet, Claude Haiku), Google Gemini models, Azure-hosted models, and local models through Ollama. This flexibility means you can start with a free-tier API key and scale to more powerful models as your agents grow in complexity.
Step 1: Install CrewAI and Create Your First Project
CrewAI provides a CLI tool that scaffolds a complete project structure for you. Start by installing CrewAI with pip, then use the CLI to create a new project. The framework uses uv as its package manager internally, which handles dependency resolution and virtual environment creation automatically.
# Install CrewAI
pip install crewai
# Verify installation
crewai version
# Create a new CrewAI project
crewai create crew research_crew
# Navigate into your project
cd research_crew
# Install project dependencies
crewai install
The crewai create crew command generates a well-organized project structure with separate YAML configuration files for agents and tasks, a main crew definition file, and a properly configured pyproject.toml. This structure follows CrewAI’s recommended pattern of separating agent configuration from business logic, making it easier to modify agents without touching Python code.
After running crewai install, your project directory looks like this:
research_crew/
├── .gitignore
├── pyproject.toml
├── README.md
└── src/
└── research_crew/
├── __init__.py
├── main.py
├── crew.py
├── config/
│ ├── agents.yaml
│ └── tasks.yaml
└── tools/
├── __init__.py
└── custom_tool.py
Common Pitfall #1: If you see a ModuleNotFoundError during installation, make sure you are using Python 3.10 or higher. Run python --version to check. CrewAI does not support Python 3.9 or earlier. If you have multiple Python versions installed, use python3.12 -m pip install crewai to target the correct version.
Step 2: Configure Your LLM Provider
Before your agents can function, you need to configure an LLM provider. CrewAI uses environment variables for API key management and supports switching between providers without changing your agent code. Set your preferred provider’s API key as an environment variable, and CrewAI will automatically route requests through LiteLLM.
# Option 1: OpenAI (most common)
export OPENAI_API_KEY="sk-your-openai-key-here"
# Option 2: Anthropic Claude
export ANTHROPIC_API_KEY="sk-ant-your-anthropic-key-here"
# Option 3: Google Gemini
export GOOGLE_API_KEY="your-google-api-key-here"
# Option 4: Local models with Ollama (free, no API key needed)
# First install Ollama from https://ollama.com
# Then pull a model:
ollama pull llama3.1
For production use, store API keys in a .env file in your project root and load them with python-dotenv. Never hardcode API keys in your Python files. CrewAI automatically reads from .env files when using the CLI runner.
To specify which model your agents should use, you configure it in the agent definition. CrewAI defaults to gpt-4o if you have an OpenAI key set, but you can override this per agent. For example, you might use a powerful model like gpt-4o for your lead research agent and a faster, cheaper model like gpt-4o-mini for summary agents. The model string follows LiteLLM’s naming convention – use anthropic/claude-sonnet-4-20250514 for Claude, gemini/gemini-2.0-flash for Gemini, or ollama/llama3.1 for local Ollama models.
Common Pitfall #2: A frequent error is AuthenticationError: Incorrect API key provided. Double-check that your environment variable is set in the current shell session. Running echo $OPENAI_API_KEY should display your key. If you are using a .env file, make sure it is in the project root directory, not a subdirectory.
Step 3: Define Your Agents in YAML
Agents are the core building blocks of any CrewAI system. Each agent has a role, a goal, and a backstory that shapes how it approaches tasks. CrewAI’s YAML-based configuration makes it easy to define and modify agents without changing Python code. Open src/research_crew/config/agents.yaml and define your agents.
# src/research_crew/config/agents.yaml
researcher:
role: >
Senior Research Analyst
goal: >
Conduct thorough research on {topic} and gather
thorough, accurate, and up-to-date information
from multiple sources.
backstory: >
You are a seasoned research analyst with 15 years of
experience in technology analysis. You are known for
your ability to find obscure but critical data points
and synthesize complex information into clear insights.
You always verify facts from multiple sources before
reporting them.
llm: openai/gpt-4o
max_iter: 15
verbose: true
writer:
role: >
Technical Content Writer
goal: >
Transform research findings on {topic} into a
well-structured, engaging, and technically accurate
article that readers will find valuable.
backstory: >
You are an award-winning technical writer who has
published over 500 articles for major tech publications.
You excel at making complex topics accessible without
sacrificing accuracy. Your writing style is clear,
concise, and engaging.
llm: openai/gpt-4o
max_iter: 10
verbose: true
editor:
role: >
Senior Content Editor
goal: >
Review and polish the article about {topic} to ensure
factual accuracy, proper structure, grammar, and
readability. Ensure all claims are supported by data.
backstory: >
You are a meticulous editor with a background in both
journalism and computer science. You have a sharp eye
for factual errors, logical inconsistencies, and unclear
writing. You never let a piece go to publication without
thorough verification.
llm: openai/gpt-4o-mini
max_iter: 8
verbose: true
Notice how each agent has a distinct personality and expertise. The {topic} placeholder gets replaced at runtime with your actual topic, making these agent definitions reusable across different research projects. The max_iter parameter controls how many reasoning loops each agent can perform – higher values allow more thorough work but cost more in API calls. Setting verbose: true lets you see each agent’s thought process in real time during execution.
You can also specify different LLM models per agent. In this configuration, the researcher and writer use gpt-4o for higher-quality reasoning, while the editor uses gpt-4o-mini since editing requires less creative reasoning and more pattern matching. This strategy can reduce your total API costs by 40-60% without sacrificing output quality on the most critical steps.
Step 4: Define Tasks in YAML
Tasks define the specific work each agent performs. Each task has a description, expected output, and is assigned to a specific agent. Tasks can also depend on other tasks, creating a workflow pipeline. Open src/research_crew/config/tasks.yaml and define your task pipeline.
# src/research_crew/config/tasks.yaml
research_task:
description: >
Research the topic: {topic}
Your research must include:
1. Current state and latest developments (2025-2026)
2. Key statistics and market data with sources
3. Technical specifications and capabilities
4. Comparison with competing solutions
5. Expert opinions and industry perspectives
6. At least 10 specific data points with numbers
Compile your findings into a structured research brief.
expected_output: >
A detailed research brief with at least 10 verified
facts, statistics, and expert quotes about {topic}.
Each fact must include its source.
agent: researcher
writing_task:
description: >
Using the research brief provided, write a thorough
technical article about {topic}.
Requirements:
- 2000+ words
- Clear introduction with hook
- 5+ sections with descriptive headings
- Data tables where appropriate
- Code examples if relevant
- Conclusion with actionable takeaways
Use only the facts from the research brief. Do not
invent or assume any data points.
expected_output: >
A complete, publication-ready technical article about
{topic} with proper formatting, data tables, and
a clear narrative structure.
agent: writer
context:
- research_task
editing_task:
description: >
Review and edit the article about {topic}.
Check for:
1. Factual accuracy against the research brief
2. Logical flow and structure
3. Grammar and style consistency
4. Data accuracy in all tables and statistics
5. Clear and compelling headline
Make corrections directly in the text and provide
a brief editor's note with key changes made.
expected_output: >
The final edited article with all corrections applied,
plus a brief editor's note listing the changes made.
agent: editor
context:
- research_task
- writing_task
The context field is crucial – it tells CrewAI that the writing task depends on the research task’s output, and the editing task depends on both. This creates a sequential pipeline where each agent builds on the previous agent’s work. Without proper context linking, agents would work in isolation and produce disconnected outputs.
Common Pitfall #3: Forgetting to link tasks with context is the most common mistake new CrewAI users make. If your writer produces content that ignores the researcher’s findings, check that context: [research_task] is properly set in your tasks YAML. Also ensure the task key names match exactly – YAML is case-sensitive.
Step 5: Build the Crew Definition
Now wire everything together in the crew definition file. The crew.py file is where you define how agents and tasks combine into a functioning crew. CrewAI provides a decorator-based API that reads your YAML configuration and assembles the crew automatically.
# src/research_crew/crew.py
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
@CrewBase
class ResearchCrew:
"""Research crew for producing high-quality articles."""
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(
config=self.agents_config["researcher"],
tools=[], # We'll add tools in Step 7
allow_delegation=False,
)
@agent
def writer(self) -> Agent:
return Agent(
config=self.agents_config["writer"],
tools=[],
allow_delegation=False,
)
@agent
def editor(self) -> Agent:
return Agent(
config=self.agents_config["editor"],
tools=[],
allow_delegation=False,
)
@task
def research_task(self) -> Task:
return Task(config=self.tasks_config["research_task"])
@task
def writing_task(self) -> Task:
return Task(config=self.tasks_config["writing_task"])
@task
def editing_task(self) -> Task:
return Task(
config=self.tasks_config["editing_task"],
output_file="output/final_article.md",
)
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)
The @CrewBase decorator integrates your class with CrewAI’s project system, automatically loading YAML configurations and wiring up agents and tasks. The Process.sequential setting tells CrewAI to run tasks one after another in the order they are defined. For tasks that can run in parallel, you can switch to Process.hierarchical, where a manager agent coordinates work across other agents.
Setting allow_delegation=False on agents prevents them from delegating their work to other agents. This is recommended when you want predictable, deterministic workflows. For more creative or exploratory tasks, setting allow_delegation=True lets agents decide at runtime whether to pass subtasks to other agents in the crew.
Step 6: Create the Main Entry Point and Run
The main entry point handles user input and kicks off the crew execution. Edit src/research_crew/main.py to accept a topic parameter and pass it to your crew. CrewAI’s input system lets you pass dynamic values that replace placeholders in your YAML configurations.
# src/research_crew/main.py
#!/usr/bin/env python
import sys
from research_crew.crew import ResearchCrew
def run():
"""Run the research crew."""
inputs = {
"topic": "AI coding assistants in 2026"
}
result = ResearchCrew().crew().kickoff(inputs=inputs)
print("nn========== FINAL OUTPUT ==========n")
print(result.raw)
print(f"nToken usage: {result.token_usage}")
def train():
"""Train the crew for a given number of iterations."""
inputs = {
"topic": "AI coding assistants in 2026"
}
try:
ResearchCrew().crew().train(
n_iterations=int(sys.argv[1]),
filename=sys.argv[2],
inputs=inputs,
)
except Exception as e:
raise Exception(f"Training error: {e}")
if __name__ == "__main__":
run()
Run your crew with the CrewAI CLI:
# Run with the CLI
crewai run
# Or run directly with Python
python -m research_crew.main
# Expected output (abbreviated):
# [2026-04-14 10:23:15] Working Agent: Senior Research Analyst
# [2026-04-14 10:23:15] Starting Task: Research the topic: AI coding assistants...
# ...
# [2026-04-14 10:24:02] Working Agent: Technical Content Writer
# [2026-04-14 10:24:02] Starting Task: Using the research brief provided...
# ...
# [2026-04-14 10:25:18] Working Agent: Senior Content Editor
# ...
# ========== FINAL OUTPUT ==========
# [The complete edited article appears here]
# Token usage: UsageMetrics(total_tokens=18432, prompt_tokens=14201, ...
The kickoff() method returns a CrewOutput object containing the final result, token usage metrics, and individual task outputs. The token_usage field is particularly useful for monitoring costs – a typical three-agent run with GPT-4o consumes 15,000-25,000 tokens depending on the topic complexity.
Common Pitfall #4: If you get CrewAI Error: Task configuration not found, check that your task method names in crew.py match exactly with the keys in tasks.yaml. For example, if your YAML has research_task:, your method must be named research_task, not researchTask or research.
Step 7: Add Tools to Your Agents
Tools are what make CrewAI agents truly powerful. Without tools, agents can only reason about their training data. With tools, they can search the web, read files, scrape websites, execute code, and interact with APIs. CrewAI includes several built-in tools and supports custom tool creation for any use case.
First, install the CrewAI tools package:
# Install CrewAI tools
pip install 'crewai[tools]'
# This installs tools for:
# - Web searching (SerperDevTool)
# - Web scraping (ScrapeWebsiteTool)
# - File reading (FileReadTool)
# - Directory reading (DirectoryReadTool)
# - PDF reading (PDFSearchTool)
# - Code execution (CodeInterpreterTool)
# - And many more
Now update your crew.py to equip agents with tools:
# Updated crew.py with tools
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import (
SerperDevTool,
ScrapeWebsiteTool,
FileReadTool,
)
@CrewBase
class ResearchCrew:
"""Research crew with tool-equipped agents."""
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(
config=self.agents_config["researcher"],
tools=[
SerperDevTool(),
ScrapeWebsiteTool(),
],
allow_delegation=False,
)
@agent
def writer(self) -> Agent:
return Agent(
config=self.agents_config["writer"],
tools=[FileReadTool()],
allow_delegation=False,
)
@agent
def editor(self) -> Agent:
return Agent(
config=self.agents_config["editor"],
tools=[],
allow_delegation=False,
)
# ... tasks and crew methods remain the same
The SerperDevTool requires a SERPER_API_KEY environment variable (free tier available at serper.dev). When the researcher agent decides it needs current information, it automatically formulates search queries, executes them through the Serper API, and incorporates the results into its reasoning. The ScrapeWebsiteTool lets the agent read full web pages when it needs deeper information than search snippets provide.
Common Pitfall #5: Giving agents too many tools slows them down and increases token usage. Each tool’s description is included in every LLM prompt, consuming tokens even when the tool is not used. Start with 2-3 essential tools per agent and add more only when you see agents struggling to complete their tasks. A focused agent with 2 tools outperforms a generalist agent with 10 tools.
Step 8: Create Custom Tools
Built-in tools cover common use cases, but real-world projects often need custom tools. CrewAI makes it straightforward to create tools that connect to your own APIs, databases, or services. Custom tools are defined as Python classes that extend BaseTool or as simple functions decorated with @tool.
# src/research_crew/tools/custom_tool.py
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field
import requests
class StockPriceInput(BaseModel):
"""Input schema for StockPriceTool."""
ticker: str = Field(
..., description="The stock ticker symbol (e.g., AAPL, MSFT)"
)
class StockPriceTool(BaseTool):
name: str = "Stock Price Lookup"
description: str = (
"Fetches the current stock price and basic financial "
"data for a given ticker symbol. Use this when you need "
"real-time stock market data."
)
args_schema: Type[BaseModel] = StockPriceInput
def _run(self, ticker: str) -> str:
"""Execute the stock price lookup."""
url = f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
meta = data["chart"]["result"][0]["meta"]
price = meta["regularMarketPrice"]
prev_close = meta["chartPreviousClose"]
change = ((price - prev_close) / prev_close) * 100
return (
f"Ticker: {ticker}n"
f"Current Price: ${price:.2f}n"
f"Previous Close: ${prev_close:.2f}n"
f"Change: {change:+.2f}%"
)
except Exception as e:
return f"Error fetching stock data for {ticker}: {str(e)}"
The Pydantic input schema provides clear documentation to the LLM about what parameters the tool expects, helping agents use tools correctly. The description field is critical – it tells the agent when and why to use this tool. Write descriptions that focus on use cases, not implementation details. To use this custom tool, add it to your agent’s tool list: tools=[StockPriceTool()].
For simpler tools, CrewAI also supports a decorator-based approach:
from crewai.tools import tool
@tool("Word Counter")
def word_counter(text: str) -> str:
"""Counts the number of words in a given text.
Use this to verify article length requirements."""
count = len(text.split())
return f"Word count: {count}"
Step 9: Implement Structured Outputs
One of CrewAI’s most powerful features – added in the 1.9.x series – is structured output support. Instead of receiving free-form text from your agents, you can define Pydantic models that enforce a specific output schema. This is essential for building reliable pipelines where downstream agents or systems need predictable data formats.
# Define structured output models
from pydantic import BaseModel, Field
from typing import List
class ResearchFinding(BaseModel):
"""A single research finding with source."""
fact: str = Field(description="The specific finding or data point")
source: str = Field(description="URL or publication name")
confidence: str = Field(
description="HIGH, MEDIUM, or LOW confidence level"
)
class ResearchReport(BaseModel):
"""Complete research report output."""
topic: str = Field(description="The research topic")
summary: str = Field(description="Executive summary in 2-3 sentences")
findings: List[ResearchFinding] = Field(
description="List of research findings"
)
key_statistics: List[str] = Field(
description="Key numerical data points"
)
recommended_sources: List[str] = Field(
description="Top sources for further reading"
)
Apply the structured output to your task definition in crew.py:
@task
def research_task(self) -> Task:
return Task(
config=self.tasks_config["research_task"],
output_pydantic=ResearchReport, # Enforce structured output
)
With structured outputs, the research agent’s result is now a validated ResearchReport object. You can access individual findings programmatically: result.pydantic.findings[0].fact. This makes it trivial to feed agent outputs into databases, APIs, or other automated systems. If the LLM output does not match the schema, CrewAI automatically retries with a corrective prompt.
Step 10: Use CrewAI Flows for Complex Orchestration
While Crews handle agent collaboration within a single workflow, CrewAI Flows let you build event-driven pipelines that chain multiple crews together with conditional logic, error handling, and state management. Flows are CrewAI’s answer to complex, multi-stage automation where you need more control than a simple sequential process provides.
# src/research_crew/flow.py
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel
from research_crew.crew import ResearchCrew
class ArticleState(BaseModel):
topic: str = ""
research_complete: bool = False
article_draft: str = ""
final_article: str = ""
word_count: int = 0
class ArticleFlow(Flow[ArticleState]):
@start()
def get_topic(self):
self.state.topic = "Multi-agent AI frameworks in 2026"
print(f"Starting flow for topic: {self.state.topic}")
@listen(get_topic)
def run_research_crew(self):
result = (
ResearchCrew()
.crew()
.kickoff(inputs={"topic": self.state.topic})
)
self.state.final_article = result.raw
self.state.word_count = len(result.raw.split())
print(f"Article complete: {self.state.word_count} words")
@listen(run_research_crew)
def validate_output(self):
if self.state.word_count < 1000:
print("WARNING: Article is too short, needs revision")
# Could trigger a revision crew here
else:
print(f"Article validated: {self.state.word_count} words")
# Save to file
with open("output/article.md", "w") as f:
f.write(self.state.final_article)
print("Article saved to output/article.md")
if __name__ == "__main__":
flow = ArticleFlow()
flow.kickoff()
Flows use a decorator-based event system. The @start() decorator marks the entry point, and @listen() decorators define which function triggers each subsequent step. State is managed through a Pydantic model (ArticleState), giving you type safety and automatic validation throughout the pipeline. This pattern is particularly powerful for production systems where you need to handle errors, implement retries, or branch logic based on intermediate results.
Common Pitfall #6: Flows and Crews serve different purposes. Use a Crew when you have a group of agents that need to collaborate on a single task. Use a Flow when you need to orchestrate multiple crews or add conditional logic between processing stages. Nesting a Flow inside a Crew (or vice versa incorrectly) leads to confusing execution patterns.
Step 11: Add Memory and Context to Your Agents
CrewAI supports multiple memory types that give agents persistence across tasks and even across crew runs. Memory allows agents to learn from past interactions, avoid repeating mistakes, and build on previous knowledge. This is especially valuable for long-running or recurring workflows.
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
memory=True, # Enable all memory types
# Memory configuration options:
# short_term_memory uses RAG for current execution
# long_term_memory persists across runs
# entity_memory tracks key entities mentioned
)
When memory is enabled, CrewAI automatically manages three memory layers. Short-term memory keeps context within the current crew execution, so agents can reference earlier findings without re-processing them. Long-term memory persists across multiple crew runs using a local SQLite database, letting agents recall information from previous executions. Entity memory specifically tracks people, organizations, and concepts mentioned during execution, building a knowledge graph that agents can query.
CrewAI also supports checkpointing through CheckpointConfig, which saves the crew’s state at each task boundary. If a long-running crew fails midway – say, due to an API rate limit – you can resume from the last checkpoint instead of re-running the entire pipeline. This feature was significantly improved in the 1.9.x releases with the addition of SqliteProvider storage for reliable state persistence.
Step 12: Implement Error Handling and Guardrails
Production CrewAI systems need strong error handling. Agents can fail due to API rate limits, network errors, malformed tool outputs, or LLM hallucinations. CrewAI provides several mechanisms for handling these scenarios gracefully.
# Production-ready crew with error handling
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@CrewBase
class ProductionCrew:
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(
config=self.agents_config["researcher"],
tools=[SerperDevTool()],
allow_delegation=False,
max_retry_limit=3, # Retry failed operations
max_iter=15, # Cap reasoning loops
respect_context_window=True, # Auto-summarize if too long
)
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
memory=True,
max_rpm=30, # Rate limit: max 30 requests per minute
planning=True, # Enable crew-level planning step
)
# Running with error handling
def main():
try:
result = ProductionCrew().crew().kickoff(
inputs={"topic": "AI frameworks comparison"}
)
logger.info(f"Crew completed. Tokens used: {result.token_usage}")
logger.info(f"Output length: {len(result.raw)} characters")
except Exception as e:
logger.error(f"Crew execution failed: {e}")
raise
The max_rpm=30 setting prevents agents from overwhelming LLM APIs with too many concurrent requests. The respect_context_window=True setting automatically summarizes intermediate outputs when they exceed the model’s context window, preventing truncation errors. The planning=True setting adds a planning phase before execution where the crew analyzes all tasks and creates an execution strategy, improving output quality for complex workflows.
Step 13: Deploy to Production with CrewAI Flows
For production deployment, wrap your CrewAI system in a FastAPI web server. This lets you trigger crew executions via HTTP requests, monitor progress, and integrate with existing systems. CrewAI’s async support means you can handle multiple crew executions concurrently.
# api_server.py - Production deployment
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from research_crew.crew import ResearchCrew
import uuid
import asyncio
app = FastAPI(title="CrewAI Research API")
# In-memory job store (use Redis in production)
jobs: dict = {}
class CrewRequest(BaseModel):
topic: str
class CrewResponse(BaseModel):
job_id: str
status: str
def run_crew(job_id: str, topic: str):
"""Run crew in background."""
try:
jobs[job_id]["status"] = "running"
result = ResearchCrew().crew().kickoff(
inputs={"topic": topic}
)
jobs[job_id]["status"] = "completed"
jobs[job_id]["result"] = result.raw
jobs[job_id]["tokens"] = str(result.token_usage)
except Exception as e:
jobs[job_id]["status"] = "failed"
jobs[job_id]["error"] = str(e)
@app.post("/research", response_model=CrewResponse)
async def start_research(
request: CrewRequest, background_tasks: BackgroundTasks
):
job_id = str(uuid.uuid4())
jobs[job_id] = {"status": "queued", "topic": request.topic}
background_tasks.add_task(run_crew, job_id, request.topic)
return CrewResponse(job_id=job_id, status="queued")
@app.get("/status/{job_id}")
async def get_status(job_id: str):
if job_id not in jobs:
return {"error": "Job not found"}
return jobs[job_id]
# Run with: uvicorn api_server:app --host 0.0.0.0 --port 8000
This deployment pattern gives you a REST API that accepts research topics, runs crew executions in the background, and provides status endpoints for monitoring progress. For production systems, replace the in-memory job store with Redis or PostgreSQL, add authentication, and implement proper logging. CrewAI’s execution is CPU-light (it is mostly waiting for LLM API responses), so a single server can handle dozens of concurrent crew runs.
CrewAI vs LangGraph vs AutoGen: Which Multi-Agent Framework to Choose
CrewAI is not the only multi-agent framework available. LangGraph and Microsoft’s AutoGen are the two main alternatives. Each framework has distinct strengths, and choosing the right one depends on your specific requirements. Here is how they compare on the dimensions that matter most for production use.
| Feature | CrewAI | LangGraph | AutoGen |
|---|---|---|---|
| Learning Curve | Low – YAML + Python | Medium – graph concepts | Medium – conversation patterns |
| Agent Definition | YAML config + decorators | Python functions as nodes | Python classes |
| Orchestration | Sequential, hierarchical, or custom | Directed graph with cycles | Conversation-based |
| Built-in Tools | 15+ (web, file, code, RAG) | Via LangChain ecosystem | Code execution, web |
| Structured Outputs | Native Pydantic support | Via LangChain | Limited |
| Memory | Short-term, long-term, entity | State persistence via checkpoints | Conversation history |
| LangChain Dependency | None (fully independent) | Required | None |
| Best For | Role-based agent teams | Complex stateful workflows | Research and prototyping |
| Python Version | 3.10-3.13 | 3.9+ | 3.8+ |
CrewAI’s biggest advantage is its role-based agent paradigm. By defining agents with roles, goals, and backstories, you get behavior that closely mirrors how human teams collaborate. LangGraph offers more control over execution flow through its graph-based approach but requires understanding directed graphs and state management concepts. AutoGen, developed by Microsoft, excels at conversational multi-agent patterns where agents communicate through natural language dialogue.
CrewAI is built entirely independent of LangChain or any other framework, which means fewer dependency conflicts and a smaller installation footprint. LangGraph, by contrast, requires the full LangChain ecosystem. For teams already invested in LangChain, LangGraph is a natural extension. For teams starting fresh or wanting minimal dependencies, CrewAI is typically the better choice.
Troubleshooting CrewAI: 10 Common Issues and Fixes
Even with careful setup, you will encounter issues when building multi-agent systems. Here are the most common problems developers face with CrewAI and their solutions.
| Issue | Cause | Solution |
|---|---|---|
| ModuleNotFoundError: No module named ‘crewai’ | Wrong Python environment active | Run which python to verify, activate correct venv |
| AuthenticationError from LLM provider | API key not set or expired | Check echo $OPENAI_API_KEY, regenerate key if expired |
| Agent stuck in infinite loop | max_iter too high or task too vague | Set max_iter=10, make task description more specific |
| Token limit exceeded | Context window overflow from long outputs | Set respect_context_window=True on agents |
| Tool execution timeout | External API slow or unreachable | Add timeout parameter to tool, implement retry logic |
| YAML parsing error | Indentation or special character issues | Use > for multiline strings, validate with yamllint |
| Task output not passed to next agent | Missing context in task config | Add context: [previous_task_name] in tasks.yaml |
| Rate limit errors (429) | Too many API requests per minute | Set max_rpm=20 on crew, add delays between agents |
| Memory database locked | Concurrent crew runs sharing SQLite | Use separate memory paths per crew instance |
| Structured output validation fails | LLM output doesn’t match Pydantic schema | Simplify schema, add field descriptions, increase max_iter |
Debugging tip: Set verbose=True on both your agents and crew to see full execution logs. CrewAI logs each agent’s thought process, tool calls, and intermediate outputs. For deeper debugging, enable Python logging at the DEBUG level: logging.basicConfig(level=logging.DEBUG). This reveals the exact prompts sent to the LLM and the raw responses received.
Performance tip: If your crew runs are slow, the bottleneck is almost always the LLM API response time. Profile your runs by checking result.token_usage – if prompt tokens far exceed completion tokens, your agent prompts may be overloaded with tool descriptions or context. Reduce tools per agent, simplify task descriptions, or switch to a faster model for non-critical agents.
Advanced Tips for Production CrewAI Systems
Once you have mastered the basics, these advanced patterns will help you build more reliable and efficient multi-agent systems. These techniques are drawn from production deployments and the CrewAI community’s best practices as of 2026.
Hierarchical Process with a Manager Agent
For complex projects where task order is not predetermined, use Process.hierarchical. CrewAI automatically creates a manager agent that delegates tasks to other agents based on their roles and capabilities. The manager decides which agent should handle each subtask and can reassign work if an agent’s output is unsatisfactory.
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.hierarchical,
manager_llm="openai/gpt-4o", # Use a powerful model for management
verbose=True,
)
Conditional Task Execution
Use task callbacks to implement conditional logic. For example, skip the editing step if the writing output already meets quality thresholds:
def check_quality(output):
"""Callback that runs after a task completes."""
word_count = len(output.raw.split())
if word_count Task:
return Task(
config=self.tasks_config["writing_task"],
callback=check_quality,
)
Cost Optimization Strategies
Multi-agent systems can become expensive if not optimized. Use cheaper models for simple tasks (editing, formatting) and reserve expensive models for complex reasoning (research, analysis). Enable caching to avoid duplicate LLM calls – CrewAI caches tool results and can cache LLM responses when the same prompt is repeated. Monitor token usage per agent to identify which agents are consuming the most resources and optimize their prompts accordingly.
CrewAI Project Template: Complete Working Example
Here is the complete project structure with all files needed for a working CrewAI research crew. You can clone this pattern and customize it for any multi-agent use case. This template incorporates all the patterns covered in this tutorial – structured outputs, tools, memory, and error handling.
# Complete pyproject.toml
[project]
name = "research_crew"
version = "0.1.0"
description = "Multi-agent research crew built with CrewAI"
requires-python = ">=3.10,=1.9.0",
"python-dotenv>=1.0.0",
]
[project.scripts]
research_crew = "research_crew.main:run"
train = "research_crew.main:train"
# .env file (add to .gitignore!)
OPENAI_API_KEY=sk-your-key-here
SERPER_API_KEY=your-serper-key-here
With this template, you can get a complete multi-agent crew running in under 10 minutes. Start with the basic three-agent research crew, test it with a simple topic, then gradually add tools, structured outputs, and memory as your requirements grow. The YAML-based configuration means you can experiment with different agent personalities and task descriptions without modifying any Python code.
Related Coverage
More AI and Developer Tutorials
- How to Build an AI Agent with LangGraph Python in 14 Steps [2026]
- How to Build a RAG Chatbot with Python and LangChain [2026]
- How to Build an MCP Server with Python: 12-Step Tutorial with FastMCP [2026]
- How to Build an AI Chatbot with Vercel AI SDK in 12 Steps [2026]
- How to Build a REST API with FastAPI [2026]
- AI Coding Tools in 2026: How Generative Code Is Transforming Software Development
Frequently Asked Questions About CrewAI
What Python version does CrewAI require?
CrewAI requires Python 3.10 or higher and supports up to Python 3.13. The framework is not compatible with Python 3.9 or earlier. If you are using a system Python that is too old, install Python 3.12 or 3.13 using pyenv or your operating system’s package manager and create a virtual environment with the correct version.
Is CrewAI free to use?
Yes, CrewAI is an open-source Python framework available on PyPI and GitHub under the MIT license. You can use it for personal and commercial projects without any licensing fees. However, you will need to pay for the LLM API calls your agents make (OpenAI, Anthropic, etc.), unless you use free local models through Ollama.
How does CrewAI compare to LangGraph?
CrewAI and LangGraph take fundamentally different approaches. CrewAI uses a role-based paradigm where agents are defined by their roles, goals, and backstories. LangGraph uses a graph-based paradigm where agents are nodes in a directed graph with explicit edges defining execution flow. CrewAI is easier to learn and has no LangChain dependency, while LangGraph offers more granular control over execution flow and state management.
Can CrewAI work with local LLMs?
Yes, CrewAI supports local LLMs through Ollama integration. Set the model parameter to ollama/llama3.1 or any other Ollama-hosted model in your agent configuration. Local models are free to use but may produce lower-quality outputs compared to cloud models like GPT-4o or Claude Sonnet. For production use, cloud models are recommended for complex reasoning tasks while local models work well for simpler tasks like formatting or summarization.
How much does it cost to run a CrewAI crew?
Cost depends entirely on your LLM provider and the complexity of your tasks. A typical three-agent crew with GPT-4o consumes 15,000-25,000 tokens per run, costing approximately $0.10-$0.25. Using GPT-4o-mini for non-critical agents reduces costs by 60-80%. You can monitor exact token usage through the result.token_usage attribute returned by every crew execution.
Can I use CrewAI with Anthropic Claude models?
Yes, CrewAI supports Anthropic Claude models through its built-in LiteLLM integration. Set your ANTHROPIC_API_KEY environment variable and use the model string anthropic/claude-sonnet-4-20250514 or anthropic/claude-haiku-4-5-20251001 in your agent configuration. Claude models work with all CrewAI features including tools, structured outputs, and memory.
What is the difference between CrewAI Crews and Flows?
Crews define a group of agents that collaborate on tasks within a single execution context. Flows are higher-level orchestration layers that chain multiple crews together with conditional logic, state management, and event-driven triggers. Use Crews for single-purpose agent teams and Flows for multi-stage pipelines where you need control over the execution path between stages.
How do I debug CrewAI agents that produce poor results?
Start by setting verbose=True on both agents and the crew to see the full reasoning chain. Check that task descriptions are specific enough – vague instructions produce vague outputs. Verify that tasks are properly linked with context in your YAML configuration. If an agent is ignoring tool results, check that the tool’s description clearly explains when to use it. Increasing max_iter gives agents more reasoning steps but also increases costs.
Sofia Lindström
Sofia Lindström is the Editor-in-Chief at Tech Insider, where she leads editorial strategy and oversees coverage across AI, cybersecurity, and enterprise technology. With over a decade in Swedish tech journalism, she previously served as technology editor at Dagens Industri and covered the Nordic startup ecosystem for Breakit. Sofia holds an MSc in Media Technology from KTH Royal Institute of Technology and is a frequent speaker at Web Summit and Slush. She is passionate about making complex technology accessible to business leaders.
View all articles