Multi-agent systems live or die by the reliability of the model API behind them. To crewai connect n4n.ai llm as the backend, you point CrewAI’s LLM wrapper at a single OpenAI-compatible endpoint and hand it your API key—no custom adapters required. The rest of this guide walks through a working setup from empty virtualenv to a running crew.
Prerequisites
You need Python 3.10 or newer and a working shell. Install CrewAI in a clean environment to avoid dependency conflicts with older LangChain versions:
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install crewai
You also need an API key from the gateway. Export it so the process can read it at runtime:
export N4N_API_KEY="sk-your-key-here"
Keep that variable out of source control. If you prefer a .env file, CrewAI loads it automatically when present.
Step 1: Install and verify CrewAI
Confirm the install resolves correctly before writing code:
python -c "import crewai; print(crewai.__version__)"
Any version in the 0.30+ range exposes the LLM class directly from the top-level package. Older versions required from crewai.llm import LLM. If the import fails, upgrade.
Step 2: Configure the LLM client
CrewAI’s LLM class accepts an OpenAI-style base_url, so the gateway’s endpoint drops in without patching. The final piece to crewai connect n4n.ai llm is pointing the LLM client at that URL and selecting a model with a provider/model string:
import os
from crewai import LLM
llm = LLM(
model="openai/gpt-4o-mini",
api_key=os.environ["N4N_API_KEY"],
base_url="https://api.n4n.ai/v1",
temperature=0.2,
max_tokens=2000,
)
The model field uses the gateway’s routing syntax: provider/model. Because the endpoint fronts 240+ models, you can swap openai/gpt-4o-mini for anthropic/claude-3.5-sonnet or meta-llama/llama-3.1-70b-instruct without changing any agent code. The gateway handles provider auth and translates responses to the OpenAI chat format CrewAI expects.
Step 3: Define agents and tasks
A crew is a collection of agents and the tasks they execute. Each agent takes the llm instance we just built. Below is a minimal researcher/writer pair:
from crewai import Agent, Task, Crew
researcher = Agent(
role="Senior Research Analyst",
goal="Find concise factual answers to the given question",
backstory="You specialize in distilling technical docs into bullet points.",
llm=llm,
verbose=True,
)
writer = Agent(
role="Technical Writer",
goal="Rewrite research into a clean paragraph",
backstory="You write for engineers who dislike fluff.",
llm=llm,
verbose=True,
)
research_task = Task(
description="What are the tradeoffs of prefix caching for LLM inference?",
expected_output="3 bullet points covering latency, cost, and invalidation.",
agent=researcher,
)
write_task = Task(
description="Turn the research into one tight paragraph.",
expected_output="A single paragraph under 100 words.",
agent=writer,
context=[research_task],
)
Note the context linkage: the writer consumes the researcher’s output. CrewAI resolves dependencies and runs tasks in order.
Step 4: Assemble and run the crew
Wire the agents and tasks into a Crew and kick it off synchronously:
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
verbose=True,
)
result = crew.kickoff()
print("FINAL OUTPUT:\n", result)
For long-running crews, use crew.kickoff_async() inside an asyncio loop. The verbose flag prints each agent step so you can see prompt construction and token streams.
Step 5: Verify success
Success means three things: the crew returns a string, the gateway logged usage, and the model responses are coherent.
- Process exit code 0 and printed
FINAL OUTPUTcontaining the writer’s paragraph. - Usage metering is visible if you inspect the underlying HTTP response. With the OpenAI client you can enable
log_level="debug"on the LLM to seex-usageheaders. The gateway returns per-token counts even on fallback. - No provider errors in the verbose log. If a provider is degraded, the gateway automatically retries against another routed provider; you’ll see a warning, not a hard failure.
A quick sanity check from a separate script confirms the endpoint independently:
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
)
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "ping"}],
)
print(resp.usage)
If that prints CompletionUsage with prompt_tokens and completion_tokens, the connection is solid.
Step 6: Pass routing and cache directives
The gateway honors client routing hints and forwards provider cache-control. You can send these as extra headers on the CrewAI LLM:
llm = LLM(
model="anthropic/claude-3.5-sonnet",
api_key=os.environ["N4N_API_KEY"],
base_url="https://api.n4n.ai/v1",
extra_headers={
"x-n4n-router": "prefer:anthropic; fallback:openai",
"x-n4n-cache": "ttl:3600",
},
)
This tells the router to prefer Anthropic but fall back to OpenAI if Anthropic is rate-limited, and to cache the prompt prefix for an hour. CrewAI passes extra_headers through to the underlying HTTP call unchanged. Use this when you have a stable system prompt that wastes tokens on every turn.
Troubleshooting
401 Unauthorized — Your N4N_API_KEY is missing or malformed. Print os.environ.get("N4N_API_KEY") before constructing the LLM.
Model not found — The model string must be provider/model. gpt-4o-mini alone will 404 because the gateway needs the provider namespace.
Timeout / hanging — Default request timeout in CrewAI is 60s. Set request_timeout=120 on the LLM if your tasks involve large context windows or slow providers.
Verbose output but empty result — Check expected_output on tasks. CrewAI’s parser can drop output if the model ignores the format hint. Tighten the description or lower temperature.
Closing notes on operations
Once the crewai connect n4n.ai llm wiring is done, treat the endpoint like any other OpenAI-compatible service: rotate keys via env, monitor usage headers, and pin model versions in code. Because the gateway abstracts provider outages, you can ship a crew that survives a single vendor’s degradation without a code change. The only permanent coupling is the base_url and the provider/model naming convention—both stable and cheap to update.