CrewAI organizes multi-step agent work into role-playing Agents, the Tasks they execute and a Crew that runs them in sequence or in parallel. It doesn’t ship with a model of its own: every agent needs an LLM to think with. Point that LLM at NativePort’s unified, OpenAI-compatible endpoint and the same key and balance you use for search, scraping or voice APIs also covers the model calls behind your crew, with real per-token pricing and no separate OpenAI account to manage.
This tutorial covers two configuration details you need when pointing CrewAI’s LLM class at a gateway instead of OpenAI directly, a full Agent/Task/Crew run against a real model, what the result looks like, optional planning mode and the errors you’ll hit if a step above gets skipped.
What you’ll need
- A NativePort API key. Sign up to get a key and $5 in credits, enough to build and run the crew below several times over while you get the configuration right.
- Python 3.10 or later.
pip install crewai. This tutorial is written against CrewAI 1.15.8; the two configuration points below come from how that version’sLLMclass resolves a model string, so if you’re on a materially older release, confirm the same behavior still holds before you rely on it.
Export your key as an environment variable. Don’t hardcode it in a script or commit it to source control:
export NATIVEPORT_API_KEY="<your NativePort API key>"
Configure the LLM
CrewAI’s LLM class is the object every Agent needs. Point it at NativePort with four arguments:
import os
from crewai import LLM
llm = LLM(
model="openai/gpt-5.4-mini",
provider="openai",
base_url="https://api.nativeport.ai/inference/v1",
api_key=os.environ["NATIVEPORT_API_KEY"],
max_completion_tokens=1024,
)
A couple of these arguments matter more than they look:
provider="openai"is required, not optional. CrewAI’sLLMconstructor decides which internal provider class handles a call by inspecting themodelstring itself. Left to infer on its own, it can treat everything after theopenai/prefix as the literal model name to send upstream and strip the prefix before the request goes out, so NativePort receives a baregpt-5.4-miniinstead of the fullopenai/gpt-5.4-miniid its catalog expects, and the call fails as though the model doesn’t exist. Passingprovider="openai"explicitly tells CrewAI to keep the string intact and forward it unchanged.max_completion_tokens, notmax_tokens. Thegpt-5.4-minifamily rejects the legacymax_tokensfield outright, the same way OpenAI’s own current reasoning-model line does. Set the output cap withmax_completion_tokensinstead.
Both of these are one-time fixes: set them once on the LLM object and every Agent that uses it inherits the corrected behavior.
Confirm the model before you build on it
Before wiring llm into an agent, it’s worth checking the catalog entry directly rather than assuming the model id and pricing you’re building against haven’t changed:
curl -s https://api.nativeport.ai/inference/v1/models/openai/gpt-5.4-mini \
-H "Authorization: Bearer $NATIVEPORT_API_KEY"
This returns the model’s current capabilities (whether it supports tools, streaming and tool choice), its supported_parameters and its live pricing. Checking this once, before a crew is built around a specific model id, catches a renamed or repriced model before it surprises you mid-project rather than mid-run.
Build a crew that actually runs
An LLM on its own doesn’t do anything. Give it to an Agent, hand the agent a Task, and run both through a Crew:
from crewai import Agent, Task, Crew
researcher = Agent(
role="Research analyst",
goal="Explain technical concepts clearly and concisely for a general audience",
backstory=(
"You are a research analyst who is good at turning dense technical "
"topics into short, accurate explanations."
),
llm=llm,
)
summarize_task = Task(
description="Explain what a context window is in large language models, in two sentences.",
expected_output="A two-sentence, plain-language explanation of an LLM context window.",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[summarize_task])
result = crew.kickoff()
print(result.raw)
crew.kickoff() drives the full loop: the agent receives the task description, calls openai/gpt-5.4-mini through NativePort, and the crew collects the output. It’s billed against your NativePort balance at gpt-5.4-mini’s published per-token rate ($0.75 per 1M input tokens, $4.50 per 1M output tokens), the same as any other request through the unified endpoint.
What kickoff returns
crew.kickoff() returns a CrewOutput object, not a plain string:
result.rawis the final text output, what you’ll want for a single-task crew like the one above.result.tasks_outputis a list ofTaskOutputobjects, one per task in the crew, useful once you have more than one task chained together.result.token_usagereports the token counts CrewAI accumulated across the run, which you can check against NativePort’s published per-token rate to see what a given crew costs before you scale it up.
For a single-agent, single-task crew like the example above, result.raw is almost always what you want to print or pass along.
Turning on planning
CrewAI can insert a planning step before a crew executes, where a separate LLM call sketches out how each task should be approached before the agents start. Turn it on with Crew(planning=True):
crew = Crew(
agents=[researcher],
tasks=[summarize_task],
planning=True,
planning_llm=llm,
)
planning_llm needs to be set explicitly here. Without it, CrewAI’s planning step falls back to a default LLM configuration of its own that tries to call OpenAI’s servers directly rather than reusing the llm object you already pointed at NativePort, and fails with an authentication error since no OpenAI key is present. Passing planning_llm=llm routes the planning call through the same NativePort configuration as everything else in the crew.
Troubleshooting
- Call fails as if the model doesn’t exist, even though
openai/gpt-5.4-miniis a real catalog id. You’re missingprovider="openai"on theLLMobject. Without it, CrewAI strips theopenai/prefix before the request leaves your machine, and NativePort rejects the bare model name it receives instead. - A request fails immediately with an error naming
max_tokens. Swap it formax_completion_tokens.gpt-5.4-minidoesn’t accept the legacy field name at all, so this isn’t a soft warning, the request doesn’t go through. 401on every call.NATIVEPORT_API_KEYis missing, mistyped, or was revoked. Check it’s actually exported in the environment the script runs in, not just a shell you no longer have open.402on every call. Your NativePort balance is at $0. Every request fails closed rather than degrading in quality, so this shows up as a hard error, not a shorter answer. Top up and retry.stopsequences you pass throughAgentorTaskare silently ignored. This is a current, model-family-level limitation, not a NativePort-specific bug:gpt-5.4-mini, like the rest of OpenAI’s currentgpt-5line, doesn’t support thestopparameter, and CrewAI’s own capability check reports that unsupported status for any model in that family. Don’t rely onstopsequences to bound output from this model; usemax_completion_tokensand prompt instructions instead.
Where to go next
- CrewAI’s LLM configuration docs cover every provider CrewAI supports natively, beyond the OpenAI-compatible path used here.
- OpenAI provider page: scope and pricing for the models reachable behind this same
/inference/v1route. - Pricing: the full billing model.
- NativePort vs. OpenRouter: where this gateway’s inference route stops and a model-routing gateway like OpenRouter starts.