CrewAI defaults to OpenAI’s hosted models, but the crewai switch openai to n4n.ai is a single environment variable change if you treat the gateway as a drop-in OpenAI-compatible proxy. Because the framework funnels every chat completion through the openai Python client, repointing the base URL redirects all agents, tasks, and tools without touching business logic.
Step 1: Pin your CrewAI version and inspect the LLM path
CrewAI versions before 0.28.0 relied on LangChain’s ChatOpenAI wrapper; newer releases expose a native LLM class that still respects the same environment variables. Run the following to confirm what you have:
pip show crewai | grep -i version
python -c "import crewai; print(crewai.__file__)"
If you are on an older build, upgrade first. The native LLM class reduces surprises around timeout and streaming:
pip install --upgrade crewai>=0.30.0
The reason this matters: CrewAI constructs the underlying client at agent initialization. If the base URL is wrong, you will not see the error until the first task runs. Verify the install before touching config. The native class builds an openai.OpenAI client under the hood, which means any env var that the official client reads (OPENAI_API_KEY, OPENAI_API_BASE, OPENAI_TIMEOUT) is honored verbatim. That is the lever we pull.
Step 2: Export the gateway credentials as OpenAI-compatible vars
The crewai switch openai to n4n.ai requires exactly two environment variables to replicate the default behavior: OPENAI_API_KEY and OPENAI_API_BASE. The key is the secret issued by the gateway dashboard; the base is the OpenAI-compatible endpoint. Export them in the shell that launches your Python process:
export OPENAI_API_KEY="sk-your-gateway-secret"
export OPENAI_API_BASE="https://your-gateway-endpoint/v1"
Do not set OPENAI_ORGANIZATION. CrewAI does not forward it, and some gateways reject the header. If you run inside a container, put these in the env section of your compose file or CI secret store. The moment these are set, any Agent that does not receive an explicit llm argument will call the gateway instead of OpenAI’s servers.
A common mistake is leaving a stale OPENAI_API_KEY in a .env file loaded by python-dotenv. CrewAI does not override env vars already present; the shell value wins. Print os.environ["OPENAI_API_BASE"] at startup to be sure. If you manage multiple environments, prefix the export with a guard:
if [ -z "$OPENAI_API_BASE" ]; then
export OPENAI_API_BASE="https://your-gateway-endpoint/v1"
fi
This prevents accidental calls to the public OpenAI endpoint when a developer forgets to source their profile.
Step 3: Select a model identifier the gateway can resolve
OpenAI’s client sends the model field verbatim. The gateway maps that string to one of 240+ backing models, so you can keep gpt-4o-mini or switch to a different provider’s slug like anthropic/claude-3-haiku. CrewAI lets you set a default via OPENAI_MODEL_NAME, but explicit per-agent configuration is safer:
import os
from crewai import LLM
llm = LLM(
model="openai/gpt-4o-mini",
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ["OPENAI_API_BASE"],
temperature=0.2,
timeout=60,
)
If you omit base_url on the LLM object, it falls back to OPENAI_API_BASE. Keep the model string lowercase and provider-prefixed when you want a non-OpenAI backend; the gateway’s routing directives are case-sensitive. Avoid trailing spaces—the client does not trim, and a model: "gpt-4o-mini " will return a 404 from the gateway’s model resolver.
When you migrate a production crew, keep the original OpenAI slug for one agent as a canary. That way a sudden regression is isolated to the routing layer rather than your prompt logic.
Step 4: Bind the LLM to agents and build a minimal crew
Define a researcher and a writer. The llm parameter takes the object from Step 3. This is the only code change required beyond env vars:
from crewai import Agent, Crew, Task
researcher = Agent(
role="Researcher",
goal="Find concise facts about LLM gateways",
backstory="You read docs and report only verified details.",
llm=llm,
verbose=True,
)
writer = Agent(
role="Writer",
goal="Summarize the findings in three bullet points",
backstory="You write for engineers who skim.",
llm=llm,
verbose=True,
)
task = Task(
description="Explain how a unified endpoint simplifies provider swaps.",
expected_output="Three bullets, no fluff.",
agent=researcher,
)
crew = Crew(agents=[researcher, writer], tasks=[task])
Note verbose=True prints the exact request URL and payload. That is your cheapest debugging tool. If you see https://api.openai.com/v1 in the log, the env var did not propagate. Also set memory=False for the smoke test; persistent memory adds a vector store call that can mask the primary LLM route.
If your crew uses tools, the tool executor still calls the same llm object for any reasoning steps. No separate configuration is needed—the gateway sees those calls identically.
Step 5: Run a smoke test and capture the response
Execute the crew in the same process after exporting the vars:
result = crew.kickoff()
print(result)
A successful run returns a string or structured output from the gateway. To prove the traffic left OpenAI’s network, inspect the response headers if you use a raw client, or check the gateway’s usage dashboard for per-token metering. The gateway records each call with the model slug you sent; mismatched slugs show up there immediately.
If you get a 401, the key is wrong. A 404 with model not found means the slug is not mapped—revert to gpt-4o-mini until you confirm the catalog. A 429 indicates the gateway’s upstream provider is rate-limited; this is where automatic fallback helps (see next step). Run the test twice with time to baseline latency; a gateway adds one TLS termination hop, typically <20 ms.
For asynchronous crews, use crew.kickoff_async() and await the result. The same env vars apply; nothing in the event loop changes the client construction.
Step 6: Rely on gateway-level fallback and cache hints
Because the gateway honors client routing directives and forwards provider cache-control hints, you can annotate prompts without changing CrewAI code. Set the Cache-Control header via the extra_headers param on the LLM if your version supports it:
llm = LLM(
model="openai/gpt-4o-mini",
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ["OPENAI_API_BASE"],
extra_headers={"Cache-Control": "max-age=3600"},
)
When a provider degrades, the gateway shifts the request to a healthy equivalent if you pass the appropriate routing directive in the same header. CrewAI does not surface these headers in its logs, so validate once with curl:
curl -s $OPENAI_API_BASE/models \
-H "Authorization: Bearer $OPENAI_API_KEY" | head -c 200
That lists the available model ids so you can confirm your slug before baking it into agents. If the models endpoint returns a JSON array with id fields, your base URL and key are correct. If it returns HTML, you likely hit a login page—double-check the /v1 suffix.
Verify success
You have completed the migration when all of the following hold:
echo $OPENAI_API_BASEprints the gateway URL, nothttps://api.openai.com/v1.- Agent verbose logs show POSTs to that URL.
- The gateway’s metering shows token usage for the run.
- A non-OpenAI model slug (e.g.,
anthropic/claude-3-haiku) returns valid completions through the same crew.
If those four checks pass, your multi-agent system is provider-agnostic. No further code changes are needed to swap models per task—just change the model string on the relevant LLM instance. The one-line environment switch you made at the start is the only permanent alteration; everything else is optional hardening.