NativePort
← How-to

How to use CrewAI with NativePort

Configure CrewAI's LLM, Agent, Task and Crew objects to run against NativePort's OpenAI-compatible endpoint on one API key and balance.

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’s LLM class 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’s LLM constructor decides which internal provider class handles a call by inspecting the model string itself. Left to infer on its own, it can treat everything after the openai/ prefix as the literal model name to send upstream and strip the prefix before the request goes out, so NativePort receives a bare gpt-5.4-mini instead of the full openai/gpt-5.4-mini id its catalog expects, and the call fails as though the model doesn’t exist. Passing provider="openai" explicitly tells CrewAI to keep the string intact and forward it unchanged.
  • max_completion_tokens, not max_tokens. The gpt-5.4-mini family rejects the legacy max_tokens field outright, the same way OpenAI’s own current reasoning-model line does. Set the output cap with max_completion_tokens instead.

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.raw is the final text output, what you’ll want for a single-task crew like the one above.
  • result.tasks_output is a list of TaskOutput objects, one per task in the crew, useful once you have more than one task chained together.
  • result.token_usage reports 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-mini is a real catalog id. You’re missing provider="openai" on the LLM object. Without it, CrewAI strips the openai/ 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 for max_completion_tokens. gpt-5.4-mini doesn’t accept the legacy field name at all, so this isn’t a soft warning, the request doesn’t go through.
  • 401 on every call. NATIVEPORT_API_KEY is 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.
  • 402 on 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.
  • stop sequences you pass through Agent or Task are 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 current gpt-5 line, doesn’t support the stop parameter, and CrewAI’s own capability check reports that unsupported status for any model in that family. Don’t rely on stop sequences to bound output from this model; use max_completion_tokens and prompt instructions instead.

Where to go next