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 is OpenAI's more affordable model for coding and professional work, and it differs from the tier above it in one way that shapes every integration: it defaults to no reasoning at all. Where the flagship deliberates unless told otherwise, this model answers immediately unless you ask it to think — which makes it fast and cheap by default, and easy to under-configure by accident. It brings dedicated coding-model capabilities into a general model: production-quality code, multi-file changes with fewer retries, and stronger document and spreadsheet work. It carries a context window slightly beyond a million tokens, accepts text and images, and adds a verbosity control that shapes answer length independently of reasoning depth.

OpenAI's more affordable model for coding and professional work — and the one whose defaults differ most from the tier above it.
GPT-5.4 defaults to reasoning_effort: "none". The flagship tier above it defaults to medium.
Same interface. Same five levels. Opposite starting points.
A request that omits the parameter behaves very differently across the two:
| Default | Behaviour with no reasoning_effort set | |
|---|---|---|
none |
| Answers immediately, no deliberation |
| Flagship tier | medium | Reasons before answering |
That has two consequences worth planning for.
Migrating up costs more than the model does. Moving a working integration to a higher tier without setting the effort level explicitly adds a reasoning pass to every request that did not have one. The bill changes for a reason unrelated to the model swap.
Migrating down loses quality silently. Coming the other way, requests that were deliberating stop doing so. Nothing errors; answers simply get shallower.
Set reasoning_effort explicitly on every request. On this model that is not a tuning
optimisation — it is the difference between a reasoning model and a fast one.
| Level | Behaviour |
|---|---|
none (default) | Fast, low-latency responses with no reasoning pass |
low | Light reasoning, minimal overhead |
medium | Balanced reasoning and speed |
high | Thorough multi-step reasoning |
xhigh | Maximum depth, for the hardest problems |
Validate the level in your own code before sending. Accepted values differ across models, and a value that works elsewhere may not work here.
Raise max_tokens when you raise effort. At higher levels the reasoning pass consumes output
budget, and an insufficient ceiling produces an empty response — every token spent on reasoning,
none left for the answer. This is a documented failure mode on this model.
Do not assume the ladder is monotonic. More reasoning is not reliably better on every workload.
Test none and medium against your own evaluation set before settling on a level in between.
This model exposes a verbosity parameter, independent of reasoning effort.
| Value | Output |
|---|---|
low | Concise, to the point |
medium (default) | Balanced |
high | Detailed and comprehensive |
These two controls are orthogonal and often confused. Reasoning effort governs how much the
model thinks before answering. Verbosity governs how much it writes afterwards. A short answer to a
hard question is xhigh effort with low verbosity — deliberate deeply, report briefly.
That combination is frequently what you actually want and rarely what gets configured.
| Model ID | openai/gpt-5.4 |
| Context window | 1,050,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 |
| Latency | Fast |
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 | 1050000 |
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 |
GPT-5.4 brought dedicated coding-model capabilities into a general-purpose model. OpenAI's stated improvements over its predecessor:
Coding — production-quality generation, polished front-end output, repository-specific patterns, and multi-file changes with fewer retries.
Document and spreadsheet work — business workflows where the input is a document rather than a question.
Image perception — stronger multimodal analysis, which matters for screenshots, charts, and scanned material.
Long-running execution — reduced end-to-end time across multi-step trajectories, with fewer tokens and fewer tool calls to reach the same result.
Token efficiency on tool-heavy workloads — the same work, less spent doing it. On a long agent run that compounds.
Base URL: https://api.devupai.com/v1 · Model ID: openai/gpt-5.4
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",
"messages": [
{
"role": "user",
"content": "Two services write to the same row without a transaction. Walk through the failure modes in order of likelihood and propose the smallest fix for each."
}
],
"reasoning_effort": "high",
"max_tokens": 32768
}'Note the explicit reasoning_effort. Without it this request answers immediately with no
deliberation — the wrong setting for the question being asked.
The combination the two controls exist to make possible.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEVUP_API_KEY"],
base_url="https://api.devupai.com/v1",
)
response = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[
{"role": "user", "content": "Which of these three index strategies will scale worst, and why?\n\n" + strategies},
],
max_tokens=32768, # generous: the reasoning pass shares this budget
extra_body={
"reasoning_effort": "xhigh", # think hard
"verbosity": "low", # answer briefly
},
)
print(response.choices[0].message.content)The large max_tokens is not inconsistent with verbosity: low. The reasoning pass consumes output
budget before the answer begins, and an insufficient ceiling here returns nothing at all rather than
a short answer.
Where the none default is exactly right.
response = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[
{"role": "system", "content": "Extract only fields present in the source. Use null for anything absent — never infer."},
{"role": "user", "content": document},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": True,
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": ["string", "null"]},
"total": {"type": ["number", "null"]},
"currency": {"type": ["string", "null"]},
"due_date": {"type": ["string", "null"]},
},
"required": ["invoice_number", "total", "currency", "due_date"],
"additionalProperties": False,
},
},
},
max_tokens=4096,
extra_body={"reasoning_effort": "none", "verbosity": "low"},
)Stating none explicitly even though it is the default is deliberate. It documents the intent, and
it survives a migration to a model with a different default.
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, verbosity: str = "medium", max_tokens: int = 16384) -> 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",
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
extra_body={"reasoning_effort": effort, "verbosity": verbosity},
)
return response.choices[0].message.content
ask(question, effort="medium")
ask(ticket, effort="none", verbosity="low", max_tokens=256)Validating in your own code gives you an error that names the problem, at the point where you can fix it.
import base64
with open("scanned_invoice.png", "rb") as handle:
encoded = base64.b64encode(handle.read()).decode("utf-8")
response = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{encoded}"},
},
{
"type": "text",
"text": (
"Extract every line item as JSON with fields: description, quantity, "
"unit_price, line_total. Report the currency exactly as printed. "
"Use null for any illegible field — do not infer."
),
},
],
}
],
max_tokens=8192,
extra_body={"reasoning_effort": "medium"},
)Instructing the model to return null rather than infer is not optional in document work. An
invented figure is undetectable downstream.
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",
messages: [
{
role: "system",
content:
"You are a staff engineer reviewing a migration. Report only defects that would " +
"cause data loss or downtime, each with a severity and the smallest safe fix.",
},
{ role: "user", content: migrationPlan },
],
max_tokens: 32768,
reasoning_effort: "high",
verbosity: "low",
});
console.log(response.choices[0]?.message?.content);import json
for _ in range(30): # bounded loop — always give an agent an iteration ceiling
response = client.chat.completions.create(
model="openai/gpt-5.4",
messages=messages,
tools=TOOLS,
max_tokens=32768,
extra_body={"reasoning_effort": "medium"},
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
print(message.content)
break
for call in message.tool_calls:
handler = HANDLERS.get(call.function.name)
if handler is None:
result = {"error": "unknown_tool", "name": call.function.name}
else:
try:
result = handler(**json.loads(call.function.arguments or "{}"))
except Exception as exc: # describe the failure — do not crash the loop
result = {"error": type(exc).__name__, "detail": str(exc)}
messages.append(
{
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
}
)
else:
print("Agent loop exceeded its iteration limit.")An agent loop is one place the none default is actively wrong — tool selection benefits from
deliberation. Set at least medium.
stream = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[{"role": "user", "content": "Design a retry policy for a webhook delivery system."}],
max_tokens=32768,
stream=True,
extra_body={"reasoning_effort": "high"},
)
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)The final chunk carries token counts, which is how you see how much of the output budget went to reasoning rather than to the answer.
Strong fit for coding work, document and spreadsheet processing, multi-step agentic tasks, and
high-volume paths where the none default is exactly what you want. Token efficiency on tool-heavy
workloads is a stated strength, and on long runs that compounds.
Consider a newer generation when your workload depends on knowledge after August 2025, or when a specific input has failed here. The interface is identical across the family, so the comparison costs one changed string.
Set the effort level either way. The most common mistake with this model is not choosing the wrong tier — it is accepting a default that was right for a different one.
max_tokens when you raise effort.