This crewai quickstart n4n.ai routing tutorial builds a two-agent CrewAI system that calls a single OpenAI-compatible gateway instead of hard-coding provider SDKs. You’ll stand up a researcher and a writer, run them sequentially, and see how routing directives keep model selection declarative and outage-resistant.
Prerequisites
- Python 3.10 or newer on your PATH
- A clean virtual environment (
python -m venv .venv && source .venv/bin/activate) - An API key for an OpenAI-compatible LLM gateway. We’ll use n4n.ai, which exposes one endpoint fronting 240+ models and forwarding cache-control hints, so you skip per-vendor client code.
- Basic comfort with environment variables and Python modules
Install the dependencies:
pip install crewai openai python-dotenv
Create a .env file in your project root. Never commit this.
N4N_API_KEY=sk-your-key-here
Confirm the install worked before writing agent code:
python -c "import crewai; print(crewai.__version__)"
Step 1: Point CrewAI at the gateway
CrewAI’s LLM wrapper accepts a base_url, so any OpenAI-compatible service works without custom adapters. The model string uses the provider/model convention that the gateway translates into a backend route.
from dotenv import load_dotenv
import os
from crewai import LLM, Agent, Task, Crew, Process
load_dotenv()
llm = LLM(
model="openai/gpt-4o-mini",
base_url="https://api.n4n.ai/v1",
api_key=os.getenv("N4N_API_KEY"),
temperature=0.2,
timeout=30,
max_retries=2,
)
The model field drives routing. Swap to anthropic/claude-3.5-sonnet and the same crew runs on a different backend with zero code changes beyond this string. Because the gateway honors client routing directives, you can also pass headers to bias toward cost or latency without touching agent logic.
Step 2: Define agents
Agents are role-bound LLM callers. The role, goal, and backstory fields are concatenated into the system prompt, so keep them specific. Avoid generic fluff like “expert AI”.
researcher = Agent(
role="Senior Research Analyst",
goal="Find concise, authoritative facts on {topic}",
backstory="You scan sources and extract only verifiable points. No speculation, no invented URLs.",
llm=llm,
verbose=True,
allow_delegation=False,
memory=False,
)
writer = Agent(
role="Technical Writer",
goal="Turn research into a tight 200-word Markdown brief",
backstory="You write for engineers. Short paragraphs, active voice, no filler.",
llm=llm,
verbose=True,
allow_delegation=False,
memory=False,
)
verbose=True streams agent reasoning to stdout, which is invaluable when debugging prompt drift. allow_delegation=False prevents the agent from spawning sub-agents in this simple crew. memory=False keeps the run stateless; enable it only when cross-task context matters.
Step 3: Define tasks
Tasks bind an agent to a description and an expected_output contract. The contract is not enforced structurally, but it strongly shapes the prompt. Use context to chain task outputs.
research_task = Task(
description="Research {topic} focusing on production tradeoffs and failure modes. Prioritize vendor docs.",
expected_output="Bullet list of 5 verified facts, each with a source URL.",
agent=researcher,
)
write_task = Task(
description="Write a 200-word brief from the provided research. Use a single H2 heading.",
expected_output="Markdown brief with skimmable bullets and no preamble.",
agent=writer,
context=[research_task],
)
The context array injects the prior task’s output into the writer’s prompt. Without it, the writer guesses. If you need structured data, set output_json or output_pydantic on the task instead of free text.
Step 4: Assemble and run the crew
A Crew schedules tasks. Process.sequential runs them in list order; Process.hierarchical would spin a manager agent that delegates. For a two-step pipeline, sequential is correct.
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
verbose=True,
)
if __name__ == "__main__":
result = crew.kickoff(inputs={"topic": "LLM gateway rate limiting"})
print("\n=== FINAL BRIEF ===\n")
print(result)
Run it:
python crew.py
Expected output at checkpoint
With verbose=True, you’ll first see the researcher’s raw output:
[Researcher] Task output:
- Rate limits are typically enforced per API key or per token bucket. (source: https://api.example.com/docs)
- Gateway-level 429s should trigger exponential backoff. (source: https://cloud.example.com/limits)
- Token buckets allow bursts; fixed windows cause synchronized retries. (source: https://queue.example.org/backpressure)
- Provider SDKs often retry blindly; gateways should honor Retry-After. (source: https://httpwg.org/specs/rfc7231.html)
- Per-token metering requires streaming accounting, not post-hoc counts. (source: https://llm-gw.example.net/usage)
Then the writer’s brief:
## LLM Gateway Rate Limiting
- Use token buckets, not fixed windows, to avoid thundering herds.
- Honor `Retry-After` headers; blind retries amplify load.
- Meter usage per token on the gateway, not in client glue code.
- Degrade gracefully: return 429 with context, not silent drops.
The result object prints the final Markdown. If you see the agent looping or ignoring expected_output, tighten the description verbs and lower temperature.
Step 5: Routing control and fallback
The crewai quickstart n4n.ai routing pattern stays resilient because the gateway layer handles degradation. n4n.ai honors client routing directives and provides automatic fallback when a provider is rate-limited, so the LLM config above needs no retry wrapper or circuit breaker in your agent code.
If you want explicit routing, change the model string or pass gateway-specific headers via extra_headers:
llm = LLM(
model="openai/gpt-4o-mini",
base_url="https://api.n4n.ai/v1",
api_key=os.getenv("N4N_API_KEY"),
extra_headers={"x-route-pref": "cost"},
)
This keeps provider logic out of your agent code. You can also flip the model to a cheaper one for the researcher and a stronger one for the writer by instantiating two LLM objects—routing stays declarative.
Project layout
Keep the tutorial file small; real projects separate concerns:
.
├── .env
├── crew.py # crew assembly
├── agents.py # agent factories
├── tasks.py # task definitions
└── config.py # LLM + gateway setup
Refactoring into agents.py avoids re-defining roles across experiments.
Production notes
- Set
timeoutandmax_retrieson theLLMto avoid hung crews blocking your pipeline. - Use
crew.kickoff_asyncwhen tasks are independent; sequential crews block on each step. - Meter spend via the gateway’s per-token usage (n4n.ai emits this); don’t roll your own counters from streamed chunks.
- Pin CrewAI in
requirements.txt—theLLMschema changes between minor versions. - Log the
modelstring with every run; routing bugs surface as silent quality drops, not exceptions.
The crewai quickstart n4n.ai routing setup above is deliberately minimal. Extend it with tool-using agents or a hierarchical process once the baseline runs green and you trust the prompt contracts.