n4nAI

Building a research crew with CrewAI and GPT-4o

Hands-on tutorial: build a multi-agent research crew with CrewAI and GPT-4o. Includes prerequisites, runnable Python code, and expected output checkpoints.

n4n Team3 min read559 words

Audio narration

Coming soon — every post will get a voice note here.

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 as OPENAI_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.

Tagscrewaigpt-4otutorialai-agents

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai multi-agent systems posts →