Model Library
Browse and deploy state-of-the-art AI models through the DEVUP Gateway.
Browse and deploy state-of-the-art AI models through the DEVUP Gateway.
GPT-5.4 nano is the smallest model in the GPT-5.4 family, built for the tasks where speed and cost decide whether a design is possible at all: classification, data extraction, ranking, and subagents. It answers immediately by default, with no reasoning pass, and exposes the same five effort levels as the tiers above it should a particular path need more. It accepts text and images across a 400,000-token context window and supports schema-constrained output, so structured extraction needs no parsing layer behind it. On DEVUP AI it is the model to reach for when the same operation runs thousands of times and the cost of each one is what you are actually optimising.

The smallest model in the GPT-5.4 family, built for tasks where speed and cost matter most: classification, data extraction, ranking, and subagents.
There is a category of work that is not hard, but is enormous. Sorting a support queue. Pulling four fields out of every document in an archive. Ranking fifty search results. Deciding which of three branches a request takes.
None of that needs a frontier model. All of it needs to happen thousands of times.
At that volume, cost per call is not a line item — it is the design constraint. A pipeline that is correct but too expensive per item does not ship. This tier exists so that it does.
The interface is identical to every other model in the family, so a path that starts here and turns out to need more can move up with one changed string.
reasoning_effort defaults to none — no deliberation pass, minimum latency.
For this tier that default is right almost always. Classification, extraction, ranking, and routing do not improve with deliberation; they just take longer and cost more.
The five levels remain available if a specific path needs one:
| Level | Behaviour |
|---|---|
none (default) | Immediate response, no reasoning pass |
low | Light reasoning, minimal overhead |
medium | Balanced |
high | Thorough multi-step reasoning |
xhigh | Maximum depth |
A rule worth following on this tier: if a task needs more than low here, it probably needs a
different model. Pushing a small model to high effort is usually more expensive and less reliable
than sending that path to a larger one.
Validate the level in your own code before sending. Accepted values differ across models.
Raise max_tokens if you do raise effort. The reasoning pass consumes output budget, and a tight
ceiling with a high level produces an empty response rather than a short one.
Independent of reasoning effort.
| Value | Output |
|---|---|
low | Concise, to the point |
medium (default) | Balanced |
high | Detailed and comprehensive |
On this tier, verbosity: low should be your default rather than the model's. The work here is
extraction and classification — a one-word answer, a JSON object, a ranked list. Balanced prose is
tokens you are paying for and then discarding.
| Model ID | openai/gpt-5.4-nano |
| Context window | 400,000 tokens |
| Max output | 128,000 tokens |
| Knowledge cutoff | 31 August 2025 |
| Input | Text, image |
| Output | Text |
| Reasoning effort | none (default) · low · medium · high · xhigh |
| Verbosity | low · medium (default) · high |
| Tools | Functions, web search, file search, computer use |
Specifications as published by OpenAI. Architecture is not published — no parameter counts, no layer structure, no training details, no weights.
| Capability | Value |
|---|---|
input_types | text, image |
output_types | text |
audio_input | Not supported |
video_input | Not supported |
context_window | 400000 |
max_output_tokens | 128000 |
reasoning | none, low, medium, high, xhigh — default none |
verbosity | low, medium, high |
streaming | Supported |
tool_calling | Supported |
structured_output | Supported — JSON schema |
requires_prompt | Yes — text prompt required, image optional |
Base URL: https://api.devupai.com/v1 · Model ID: openai/gpt-5.4-nano
curl https://api.devupai.com/v1/chat/completions \
-H "Authorization: Bearer $DEVUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-nano",
"messages": [
{
"role": "system",
"content": "Classify the support ticket. Reply with exactly one word: billing, technical, shipping, or other."
},
{ "role": "user", "content": "My order never arrived and the tracking page is blank." }
],
"reasoning_effort": "none",
"verbosity": "low",
"max_tokens": 8
}'An eight-token ceiling on a one-word answer. On this tier, every parameter in that request is doing cost work.
The second use OpenAI names for this model. A schema removes the parsing layer entirely.
import os
import json
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEVUP_API_KEY"],
base_url="https://api.devupai.com/v1",
)
SCHEMA = {
"type": "json_schema",
"json_schema": {
"name": "contact",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": ["string", "null"]},
"email": {"type": ["string", "null"]},
"phone": {"type": ["string", "null"]},
"company": {"type": ["string", "null"]},
},
"required": ["name", "email", "phone", "company"],
"additionalProperties": False,
},
},
}
def extract_contact(text: str) -> dict:
"""Pull typed contact fields out of unstructured text."""
response = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[
{
"role": "system",
"content": "Extract only fields present in the text. Use null for anything absent — never infer.",
},
{"role": "user", "content": text},
],
response_format=SCHEMA,
max_tokens=512,
extra_body={"reasoning_effort": "none", "verbosity": "low"},
)
return json.loads(response.choices[0].message.content)Nullable types throughout, and an explicit instruction never to infer. A schema that forbids null invites the model to produce a value where the source has none — and on an extraction pipeline running at volume, that error is invisible until someone acts on it.
The third named use. Note the constrained output shape.
def rank(query: str, candidates: list[str]) -> list[int]:
"""Return candidate indices ordered by relevance to the query."""
numbered = "\n".join(f"{i}: {c}" for i, c in enumerate(candidates))
response = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[
{
"role": "system",
"content": (
"Rank the numbered candidates by relevance to the query, most relevant first. "
"Reply with a JSON array of indices only. No prose."
),
},
{"role": "user", "content": f"Query: {query}\n\nCandidates:\n{numbered}"},
],
max_tokens=256,
extra_body={"reasoning_effort": "none", "verbosity": "low"},
)
return json.loads(response.choices[0].message.content)Ranking by index rather than by restating each candidate is what keeps the output small. A model that echoes fifty candidate strings back to you is charging you twice for the same text.
The fourth named use, and where this tier saves the most in a larger system.
def condense(raw: str) -> str:
"""Shrink a large tool result before it re-enters an expensive model's context."""
response = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[
{
"role": "system",
"content": (
"Condense this tool output to the facts a downstream agent needs. "
"Preserve every identifier, number, and error message exactly. "
"Drop formatting, repetition, and anything decorative."
),
},
{"role": "user", "content": raw},
],
max_tokens=1024,
extra_body={"reasoning_effort": "low", "verbosity": "low"},
)
return response.choices[0].message.contentA verbose tool result entering a large model's context on every turn of a long agent loop is one of the more expensive habits in agent design. Condensing it here first costs a fraction of what it saves.
Note low rather than none: deciding what to keep is a judgment, not a transformation.
Defaults differ across models. Passing the level on every call is what makes a model swap safe.
VALID_EFFORT = {"none", "low", "medium", "high", "xhigh"}
def ask(prompt: str, *, effort: str = "none", verbosity: str = "low", max_tokens: int = 512) -> str:
"""Send a request with an explicit effort level, validated before it is sent."""
if effort not in VALID_EFFORT:
raise ValueError(f"reasoning_effort must be one of {sorted(VALID_EFFORT)}, got {effort!r}")
response = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
extra_body={"reasoning_effort": effort, "verbosity": verbosity},
)
return response.choices[0].message.contentThe defaults in this helper are inverted from the model's own — none effort and low verbosity —
because on this tier that is almost always what you want.
import base64
with open("receipt.png", "rb") as handle:
encoded = base64.b64encode(handle.read()).decode("utf-8")
response = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{encoded}"},
},
{
"type": "text",
"text": "Read the total and the currency. Reply as JSON with keys total and currency, or null if unreadable.",
},
],
}
],
max_tokens=128,
extra_body={"reasoning_effort": "none", "verbosity": "low"},
)Images consume input tokens regardless of tier, so keep the question narrow. Asking for two fields rather than a full description is what keeps a visual extraction path affordable at volume.
npm install devupaiimport DevupAI from "devupai";
const client = new DevupAI({
apiKey: process.env.DEVUP_API_KEY,
});
const response = await client.chat.completions.create({
model: "openai/gpt-5.4-nano",
messages: [
{
role: "system",
content: "Classify the support ticket. Reply with exactly one word: billing, technical, shipping, or other.",
},
{ role: "user", content: ticket },
],
max_tokens: 8,
reasoning_effort: "none",
verbosity: "low",
});
console.log(response.choices[0]?.message?.content);stream = client.chat.completions.create(
model="openai/gpt-5.4-nano",
messages=[{"role": "user", "content": "Summarise this in two sentences."}],
max_tokens=256,
stream=True,
extra_body={"reasoning_effort": "none", "verbosity": "low"},
)
for chunk in stream:
if not chunk.choices:
if getattr(chunk, "usage", None):
print(f"\n\nTokens — in: {chunk.usage.prompt_tokens}, out: {chunk.usage.completion_tokens}")
continue
delta = chunk.choices[0].delta
if getattr(delta, "content", None):
print(delta.content, end="", flush=True)Streaming matters less on this tier than anywhere else — responses are short and immediate by design. It is still useful for reading token counts while tuning a high-volume path.
This tier is chosen for throughput, so the things that go wrong are throughput problems.
Set a tight max_tokens. A one-word classification does not need a 4,096-token ceiling. The
ceiling is not what you pay, but it is what an unexpected response can cost.
Use schemas rather than parsing prose. Schema-constrained output removes both a failure mode and a post-processing step.
Debounce anything triggered by user input. A search box or autocomplete that fires on every keystroke is the classic way to multiply a cheap model into an expensive bill.
Batch where the shape allows it. Classifying twenty short items in one request costs far less in overhead than twenty requests.
Watch the input side. On short outputs, the prompt frequently costs more than the completion. A long system prompt repeated across thousands of calls is the single largest line in many high-volume pipelines.
Cap retries. A failing item retried without a ceiling is a loop.
Strong fit for classification, extraction, ranking, routing, tagging, deduplication, and subagents — anywhere the operation is simple and the count is large.
Move up a tier when a task needs judgment rather than transformation, when quality on a specific input is not good enough, or when you find yourself raising the effort level to compensate.
The escalation signal is clear: if you are running this model above low effort to get the
result you want, the next tier is usually cheaper and more reliable than the setting you are
reaching for.