NativePort
← How-to

How to use Agno with NativePort

Build a tool-calling Agno agent on NativePort's OpenAI-compatible inference endpoint: setup, a runnable example, streaming, and tool safety.

Agno is an open-source Python framework for building agents: a single Agent object wraps a model, a set of tools, and the instructions that govern how it uses them. Agno doesn’t ship its own inference; it calls out to whichever model provider you configure. Pointed at NativePort’s OpenAI-compatible endpoint, an Agno agent gets tool-calling access to a metered model with no separate provider account and no SDK beyond agno and openai. This tutorial builds a small tool-using agent from scratch, runs it, and covers the parts of the setup that are easy to get wrong the first time.

What you’ll need

  • A NativePort API key. Sign up to get an API key and $5 in credits.
  • Python 3.9 or later.
  • pip install agno==2.8.5 openai. Agno’s OpenAI-compatible model class builds on the openai package’s HTTP client, so it needs to be installed even though you’re not calling OpenAI directly.

Export your key as an environment variable. Don’t hardcode it in a script you might commit or share:

export NATIVEPORT_API_KEY="<your NativePort API key>"

Point Agno at NativePort

Agno talks to any OpenAI-compatible backend through OpenAILike, a model class built specifically for third-party endpoints that speak the OpenAI chat-completions contract. Import it from agno.models.openai.like, not from agno.models.openai directly:

import os
from agno.models.openai.like import OpenAILike

model = OpenAILike(
    id="openai/gpt-5.4-mini",
    api_key=os.environ["NATIVEPORT_API_KEY"],
    base_url="https://api.nativeport.ai/inference/v1",
    max_completion_tokens=1024,
)

A few details matter here:

  • id="openai/gpt-5.4-mini" is NativePort’s canonical model id for the unified inference endpoint. The openai/ prefix selects the route; everything after it is OpenAI’s own model name. OpenAILike forwards this string to the gateway unchanged, so use the full prefixed id, not a bare gpt-5.4-mini.
  • base_url must point at /inference/v1, NativePort’s unified inference path. This is the same endpoint the standard openai Python client would use directly; OpenAILike is Agno’s wrapper around that same contract.
  • 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 families do. OpenAILike exposes both fields; pass the current one.

Build a tool-using agent

An agent becomes useful once it can call something outside the model itself. Give it one small, deterministic Python function as a tool, plus instructions that tell it when to use that function instead of guessing:

from agno.agent import Agent


def get_shipping_status(order_id: str) -> str:
    """Look up the shipping status for an order id."""
    orders = {
        "A100": "shipped",
        "A101": "processing",
        "A102": "delivered",
    }
    return orders.get(order_id, "unknown order id")


agent = Agent(
    model=model,
    instructions=(
        "You help customers check order status. "
        "Always call get_shipping_status to answer questions about a specific order id. "
        "Never guess a status yourself."
    ),
    tools=[get_shipping_status],
)

Agno turns a plain Python function into a callable tool automatically, inferring the tool’s schema from the function’s signature and docstring. Keep the function itself deterministic and side-effect-free, as here, so the behavior you see is the model’s tool-use decision, not variance in what the tool returns.

Run it

Agent.run() sends the input through the model, executes any tool calls the model asks for, and returns a single result object once the turn is complete:

result = agent.run("What's the status of order A100?")
print(result.content)

A model instructed to use the tool for this question calls get_shipping_status("A100"), gets back "shipped", and folds that into a final reply, something like "Order A100 has shipped.". To confirm the tool actually fired rather than the model answering from a guess, inspect result.tools:

for call in result.tools or []:
    print(call.tool_name, call.tool_args, "->", call.result)

That prints get_shipping_status {'order_id': 'A100'} -> shipped when the call went through as expected. If result.tools is empty, the model answered without calling anything, which for this instruction and question means the setup, not the model, is worth rechecking first.

Expected result shape

agent.run() returns a RunOutput object, not a plain string. The fields worth knowing:

  • content: the final assistant reply as a string. This is almost always what you want.
  • tools: a list of tool calls made during the run, each with tool_name, tool_args, and result.
  • messages: the full message history for the run, including the tool-call and tool-result messages the model and Agno exchanged.
  • model: the model id the run actually used, useful to confirm against what you passed to OpenAILike.
  • status: the run’s terminal status.

Build against these fields directly rather than assuming agent.run() returns a bare string; code that does str(agent.run(...)) will work by accident on some paths and confuse you when a tool call is involved.

Streaming

For a response you want to display as it arrives, pass stream=True and iterate the result. Agno emits a sequence of typed events rather than raw text chunks; filter for RunContentEvent and read its content field:

from agno.run.agent import RunContentEvent

for event in agent.run("What's the status of order A101?", stream=True):
    if isinstance(event, RunContentEvent) and event.content:
        print(event.content, end="", flush=True)
print()

Other event types show up in the same stream, tool-call-started and tool-call-completed events among them, since Agno reports the whole run’s lifecycle, not just the text. Filtering to RunContentEvent is what gets you just the assistant’s visible reply.

Tool safety

get_shipping_status above is read-only and deterministic on purpose. Before wiring in a tool with real consequences, keep this in mind:

  • Treat a tool’s arguments as coming from the model, not from a trusted caller. Validate order_id-style inputs before using them in a database query, file path, or shell command, exactly as you would for user input arriving from any other untrusted source.
  • Anything a tool can do, the agent can trigger on its own initiative, based on how it interprets a prompt. Before wiring in a tool that writes data, spends money, or calls another service, decide whether that action needs a confirmation step outside the agent loop, and keep NATIVEPORT_API_KEY itself out of tool code entirely, since tools run inside the same process and don’t need it.

Troubleshooting

  • Authentication errors. A missing, malformed, or revoked NATIVEPORT_API_KEY surfaces as an authentication failure from the OpenAI client underneath OpenAILike, reported back through Agno as an API status error with an unauthorized message. Confirm the key is exported in the same environment the script runs in, and that it hasn’t been rotated since you copied it.
  • Model id typos. Use the full prefixed id, openai/gpt-5.4-mini, in OpenAILike(id=...). A bare gpt-5.4-mini without the prefix targets a different route than the one this tutorial’s base_url serves, and fails rather than silently falling back to the right model.
  • Legacy token arguments. If you’re adapting an older Agno example or a snippet written for a different provider, check it for max_tokens. The gpt-5.4-mini family rejects that field; max_completion_tokens is the one to set, as used above.
  • Instructions not being followed. OpenAILike is a generic wrapper over any OpenAI-compatible backend, and instruction-following can vary by which model and provider sit behind it, since not every backend implements the chat-completions contract identically. This is a real, model-dependent variation reported against Agno’s OpenAILike path in general, not something specific to or caused by NativePort. If you swap in a different model id, verify with a direct check, like the get_shipping_status call above, that instructions are still being honored before relying on it, rather than assuming parity with gpt-5.4-mini.

What this costs

openai/gpt-5.4-mini is metered at $0.75 per 1M input tokens and $4.50 per 1M output tokens, NativePort’s real pass-through rate with no per-call markup. Check result.metrics on the RunOutput for the token counts behind any given run if you want to budget against your own workload. A zero balance fails every request with 402 rather than degrading output quality. See pricing for the full billing model.

Where to go next

  • OpenAI provider page: scope and pricing range for the models behind this route, including the /inference/v1 contract this connection runs on.
  • Pricing: the full billing model.
  • OpenRouter alternative comparison: what NativePort’s inference route adds beyond model calls, and where it still falls short.