If you’re working through crewai n4n.ai getting started, the fastest path is to treat the gateway as a drop-in OpenAI-compatible endpoint and point CrewAI’s LLM wrapper at it. This tutorial builds a two-agent research and writing crew that runs entirely against that endpoint, with no framework forks or custom adapters.
Prerequisites
- Python 3.10 or newer
pipand a clean virtual environment- An API key for the gateway, exported as
N4N_API_KEY - The gateway base URL
https://api.n4n.ai/v1(OpenAI-compatible)
CrewAI installs cleanly from PyPI. The only integration work is telling its LLM class where to send requests and which model name to route.
Step 1: Install CrewAI
Create and activate a virtual environment, then install the framework:
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install crewai
Verify the import works:
python -c "import crewai; print(crewai.__version__)"
Expected output is a version string like 0.30.0 (exact number depends on release).
Step 2: Export credentials
CrewAI reads standard OpenAI-style environment variables if you let it, but we’ll be explicit in code. Still, export the secrets so they don’t land in source control:
export N4N_API_KEY="sk-your-real-key"
export N4N_BASE_URL="https://api.n4n.ai/v1"
If you use a .env file, load it with python-dotenv before constructing the LLM.
Step 3: Construct the LLM handle
CrewAI’s LLM class wraps LangChain’s ChatOpenAI and accepts base_url. The gateway routes by model string, so you can request a provider/model pair directly.
import os
from crewai import LLM
llm = LLM(
model="anthropic/claude-3.5-sonnet",
base_url=os.environ["N4N_BASE_URL"],
api_key=os.environ["N4N_API_KEY"],
temperature=0.2,
max_tokens=1024,
)
The model field is forwarded unchanged. A string like openai/gpt-4o-mini or meta-llama/llama-3.1-70b-instruct works as long as the gateway addresses it. Because the endpoint is OpenAI-compatible, CrewAI’s token counting and streaming behave exactly as they would against OpenAI.
Step 4: Define agents and tasks
We’ll build a research analyst and a technical writer. The writer depends on the researcher’s output via context.
from crewai import Agent, Task, Crew
researcher = Agent(
role="Research Analyst",
goal="Find concise facts about the Python GIL",
backstory="You are a meticulous engineer who cites sources.",
llm=llm,
verbose=True,
)
writer = Agent(
role="Technical Writer",
goal="Turn research into a short briefing",
backstory="You write crisp internal docs for backend teams.",
llm=llm,
verbose=True,
)
research_task = Task(
description="Summarize what the Python GIL is and why it matters in 3 bullet points.",
expected_output="Three bullet points with factual claims about the GIL.",
agent=researcher,
)
write_task = Task(
description="Convert the research into a 100-word internal briefing in markdown.",
expected_output="A 100-word markdown briefing referencing the bullets.",
agent=writer,
context=[research_task],
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
verbose=True,
)
Step 5: Run the crew
Kick off execution synchronously:
result = crew.kickoff()
print("=== FINAL RESULT ===")
print(result)
You’ll see agent thinking logs (because verbose=True) and then the final concatenated output. A representative truncated run looks like:
[Research Analyst] Task output:
- The GIL is a mutex that prevents multiple native threads from executing Python bytecodes simultaneously.
- It simplifies CPython memory management but blocks true multi-core parallelism for CPU-bound code.
- Workers often use multiprocessing or offload to C extensions to bypass it.
[Technical Writer] Task output:
# Python GIL Briefing
The Global Interpreter Lock (GIL) is a CPython mutex serializing bytecode execution. While it eases memory safety, it caps CPU-bound throughput on multi-core hosts. Teams typically adopt multiprocessing or native extensions to recover parallelism.
=== FINAL RESULT ===
# Python GIL Briefing
...
The result object exposes .raw and .tasks_output for programmatic consumption.
Step 6: Inspect usage and routing
The gateway returns OpenAI-compatible usage metadata. CrewAI doesn’t surface it directly on the result, but you can wrap the LLM call or inspect logs. If you need per-token metering, capture the response headers from a lower-level client:
from openai import OpenAI
client = OpenAI(
base_url=os.environ["N4N_BASE_URL"],
api_key=os.environ["N4N_API_KEY"],
)
resp = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "ping"}],
)
print(resp.usage.model_dump())
This prints prompt_tokens, completion_tokens, and total_tokens. The same metering applies to CrewAI runs because every agent turn goes through that endpoint.
Handling degradation without code changes
The OpenAI-compatible gateway you configured handles automatic fallback when an upstream provider is rate-limited or degraded. If you specify model="openai/gpt-4o-mini" and that provider errors, the request is retried against a healthy route without modifying your CrewAI definition. You can also send provider cache-control hints via the extra_headers parameter on the LLM if you want to reuse prompt prefixes across agent steps:
llm = LLM(
model="anthropic/claude-3.5-sonnet",
base_url=os.environ["N4N_BASE_URL"],
api_key=os.environ["N4N_API_KEY"],
extra_headers={"x-cache-control": "ephemeral"},
)
CrewAI forwards extra_headers to the underlying client, so the gateway honors your directive.
Debugging common failures
401 Unauthorized – Your N4N_API_KEY is missing or malformed. Print os.environ.get("N4N_API_KEY") outside production.
404 Model not found – The model string isn’t addressed by the gateway. List supported routes from your dashboard or hit the /v1/models endpoint.
Timeout on kickoff – Agent verbose mode helps, but set LLM(..., timeout=60) to avoid default 10s hangs on slow provider routes.
Context overflow – Long agent backstories plus task descriptions eat tokens fast. Keep max_tokens modest and trim backstory to one sentence.
Running async
For concurrent crews, use crew.kickoff_async():
import asyncio
async def main():
result = await crew.kickoff_async()
print(result)
asyncio.run(main())
The same llm object is safe to share across agents; the underlying HTTP client is thread-safe and connection-pooled.
Wrapping up
You now have a reproducible pattern: install CrewAI, bind its LLM to the OpenAI-compatible endpoint, and describe agents/tasks normally. No custom transport code is required to get multi-agent workflows with fallback and per-token metering. From here, extend the crew with a third validator agent, or swap the model string to compare provider quality on the same tasks.