This tutorial walks through building a CrewAI research agent GPT-4o pipeline that delegates web research, drafting, and fact-checking to specialized agents. You’ll end up with a reproducible Python script that runs a sequential multi-agent crew against the OpenAI API.
Prerequisites
- Python 3.10 or newer
- An OpenAI API key with access to
gpt-4o(export asOPENAI_API_KEY) - A Serper.dev API key for web search (export as
SERPER_API_KEY) — CrewAI’s search tool wraps this service - Basic familiarity with Python and async isn’t required; CrewAI handles orchestration synchronously by default
If you prefer not to provision Serper, swap the tool for a stub that returns hardcoded text; the agent logic stays identical.
Install dependencies
Create a virtual environment and install the pinned major versions:
python -m venv .venv
source .venv/bin/activate
pip install "crewai==0.28.0" "crewai-tools==0.1.0" openai==1.30.0
Version pinning matters. CrewAI’s agent signature changed across 0.2x releases; the code below targets the 0.28 line.
Define the search tool
CrewAI separates reasoning (the LLM) from action (tools). For a research crew, the primary action is retrieval. Use the bundled SerperDevTool:
from crewai_tools import SerperDevTool
search_tool = SerperDevTool()
This tool requires SERPER_API_KEY in the environment. It returns a structured blob of organic results, which gpt-4o can parse inside the agent loop.
Build the agents
A three-agent topology is enough for credible research: a finder, a writer, and a skeptic. Each agent gets a distinct role, goal, and backstory — these strings are injected into the system prompt, so write them like job descriptions, not commands.
Researcher
from crewai import Agent
researcher = Agent(
role="Senior Research Analyst",
goal="Find authoritative sources on {topic} and extract verifiable facts",
backstory=(
"You have a PhD in library science and ten years at a news desk. "
"You cite primary sources and never infer without evidence."
),
tools=[search_tool],
llm="gpt-4o",
verbose=True,
)
The llm="gpt-4o" string tells CrewAI to use ChatOpenAI(model="gpt-4o") under the hood, pulling credentials from OPENAI_API_KEY.
Writer
writer = Agent(
role="Technical Writer",
goal="Draft a concise, readable brief from the researcher's findings",
backstory=(
"You write for engineers. You avoid hype, use bullet points, "
"and always attribute claims to the source provided."
),
llm="gpt-4o",
verbose=True,
)
No tools for the writer — it consumes the prior task’s output via task context.
Editor
editor = Agent(
role="Fact Checker",
goal="Verify every claim in the brief against the supplied sources",
backstory=(
"Former copy editor with a distrust of adjectives. "
"You flag missing citations and logical leaps."
),
llm="gpt-4o",
verbose=True,
)
The CrewAI research agent GPT-4o setup benefits from this third node; without it, the writer tends to smooth over retrieval gaps.
Define tasks and crew
Tasks are the units of work. They reference agents and can depend on previous tasks via context.
from crewai import Task, Crew, Process
research_task = Task(
description="Research the topic: {topic}. Collect at least 5 sources with URLs.",
expected_output="Bullet list of sources, each with a URL and 2-3 key facts.",
agent=researcher,
)
write_task = Task(
description="Write a 300-word Markdown brief using only the research findings.",
expected_output="Markdown brief with an H2 heading and a Sources section.",
agent=writer,
context=[research_task],
)
edit_task = Task(
description="Review the brief. Correct unsupported claims and list changes made.",
expected_output="Final brief plus a short 'Corrections' list.",
agent=editor,
context=[write_task],
)
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, write_task, edit_task],
process=Process.sequential,
)
Process.sequential runs tasks in order; the context arrays make outputs explicit rather than relying on conversation memory.
Run the crew
Wrap execution in a main block so the script is importable:
if __name__ == "__main__":
result = crew.kickoff(inputs={"topic": "post-quantum cryptography standards"})
print("\n=== FINAL CREW OUTPUT ===")
print(result)
Run it:
python research_crew.py
Expected output
With verbose=True, you’ll see per-agent thought traces. The final printed object is a CrewOutput whose raw attribute contains the editor’s text. A representative tail:
=== FINAL CREW OUTPUT ===
## Post-Quantum Cryptography Standards
NIST finalized FIPS 203 (ML-KEM) in 2024, standardizing a lattice-based KEM...
### Sources
- NIST FIPS 203: https://csrc.nist.gov/pubs/fips/203/final
- ...
### Corrections
- Removed claim that AES-256 is quantum-broken; no source supported it.
If you see RateLimitError, your gpt-4o tier is throttling concurrent tool calls. Lower max_rpm on the agent or batch tasks.
Production considerations
Handle provider failures
CrewAI does not retry failed LLM calls by default. Wrap kickoff in tenacity:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def run_crew(topic: str):
return crew.kickoff(inputs={"topic": topic})
Use an OpenAI-compatible gateway
If you’d rather not manage OpenAI outage windows, point CrewAI at an OpenAI-compatible endpoint that fronts multiple providers. For example, n4n.ai exposes one endpoint covering 240+ models with automatic fallback when a backend is degraded, and it honors the same model field — so swapping llm="gpt-4o" for a custom base URL is a one-line LLM config change via LangChain’s ChatOpenAI(openai_api_base=...). Keep per-token metering enabled to track agent spend.
Constrain token bleed
Research crews loop: agent → tool → agent. Set max_iter on each agent to 5–8. Unbounded iterations turn a $0.02 run into a $2 run when the search tool returns garbage.
researcher = Agent(
# ... same fields ...
max_iter=6,
)
Cache tool results
Serper charges per query. Cache the search_tool output with a simple functools.lru_cache wrapper keyed by topic if you re-run the same crew in tests.
The CrewAI research agent GPT-4o pattern is stable for internal knowledge bases, competitive scans, and draft-zero documentation. The moment you add a fourth agent that writes code, isolate it in its own crew — cross-pollinating writing and execution contexts degrades both.