Strukturierte JSON-Outputs über OpenAI-kompatible APIs sauber definieren
Mit Structured Outputs lassen sich JSON-Antworten über ein festes Schema erzwingen statt nur ein returntes JSON zu erhoffen. Ein Praxisleitfaden zeigt, wie Responses und Chat Completions mit Validierungsschichten, Fehlerbehandlung und Kompatibilitätstests zwischen verschiedenen Routen abgesichert werden.

Veröffentlicht am 16. Okt. • Zuerst erschienen auf modelflare.dev
Ein praxisnaher Leitfaden zu strikten JSON-Schema-Ausgaben über Responses und Chat Completions, inklusive Validierungsebenen, Fehlerbehandlung und Tests der Routen-Kompatibilität.
Structured Outputs ermöglichen einer Anwendung, JSON anzufordern, das einem definierten Schema folgt, statt das Modell lediglich zu bitten, „JSON zurückzugeben“. Mit einer OpenAI-kompatiblen API kann dasselbe Schema über Responses oder Chat Completions verwendet werden, allerdings unterscheiden sich die Anfragefelder, und die Kompatibilität muss für das ausgewählte Modell und die gewählte Route verifiziert werden.
Das sichere Produktionsmuster besteht aus drei Ebenen: strikte Schema-Ausgabe verwenden, wenn das Modell sie unterstützt, das zurückgegebene JSON in der Anwendung parsen und validieren und anschließend Geschäftsregeln auf die Werte anwenden. Schema-Konformität beseitigt viele Formatierungsfehler; sie beweist jedoch nicht, dass die Antwort des Modells inhaltlich oder semantisch korrekt ist.
Es gibt drei gängige Wege, ein Modell um eine JSON-ähnliche Ausgabe zu bitten.
Der OpenAI-Leitfaden zu Structured Outputs empfiehlt Structured Outputs anstelle des JSON-Modus, wenn das gewählte Modell und der Endpunkt dies unterstützen. Derselbe Leitfaden unterscheidet zudem zwei verschiedene Aufgaben:
Dieser Artikel konzentriert sich auf die erste Aufgabe. Eine spätere Tool-Schleife kann strenge Argument-Schemas wiederverwenden, aber ihre Call-IDs und Ergebnisnachrichten folgen einem anderen Vertrag.
Betrachten wir einen Workflow für das Support-Triage. Die Anwendung benötigt vier Felder:
{ "type": "object", "properties": { "category": { "type": "string", "enum": [ "billing", "technical", "account" ] }, "priority": { "type": "integer", "minimum": 1, "maximum": 3 }, "requires_human": { "type": "boolean" }, "summary": { "type": "string" } }, "required": [ "category", "priority", "requires_human", "summary" ], "additionalProperties": false } Enter fullscreen mode Exit fullscreen mode Wenn jede Eigenschaft als erforderlich markiert und additionalProperties auf false gesetzt wird, entsteht ein stabiles Objekt für nachgelagerten Code. Das macht jedoch nicht jedes mögliche JSON-Schema-Keyword portabel. OpenAI unterstützt eine dokumentierte Teilmenge, und ein anderer OpenAI-kompatibler Anbieter unterstützt möglicherweise eine andere Teilmenge oder gar keinen strikten Schema-Modus.
Die Responses-API legt das Antwort-Schema unter text.format ab. Legen Sie einen Modelflare-API-Schlüssel fest und wählen Sie ein Modell, das aktuell für Ihren API-Schlüssel gelistet und für Structured Outputs verifiziert ist.
export MODELFLARE_API_KEY = "<YOUR_API_KEY>" export MODEL_ID = "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>" curl -sS https://modelflare.dev/v1/responses \ -H "Authorization: Bearer $MODELFLARE_API_KEY " \ -H "Content-Type: application/json" \ -d "{ \" model \": \" $MODEL_ID \", \" input \": \" The customer was charged twice and wants a refund. \", \" text \": { \" format \": { \" type \": \" json_schema \", \" name \": \" support_ticket \", \" schema \": { \" type \": \" object \", \" properties \": { \" category \": { \" type \": \" string \", \" enum \": [ \" billing \", \" technical \", \" account \" ] }, \" priority \": { \" type \": \" integer \", \" minimum \": 1, \" maximum \": 3 }, \" requires_human \": { \" type \": \" boolean \" }, \" summary \": { \" type \": \" string \" } }, \" required \": [ \" category \", \" priority \", \" requires_human \", \" summary \" ], \" additionalProperties \": false }, \" strict \": true } } }" Enter fullscreen mode Exit fullscreen mode In einer Responses-Antwort erscheinen generierte Inhalte in typisierten Ausgabe-Items. SDKs bieten möglicherweise Komfort-Helfer wie aggregierten Ausgabetext oder geparste Ausgabe an, aber ein HTTP-Client sollte nicht davon ausgehen, dass das strukturierte Objekt ein Feld auf oberster Ebene ist. Lesen Sie das vollständige Antwortobjekt, suchen Sie seinen Textausgabe-Eintrag und parsen Sie diesen Text als JSON.
Bei Chat Completions liegt das Antwort-Schema unter response_format.json_schema. Der Schema-Inhalt bleibt unverändert; nur die Hülle unterscheidet sich.
curl -sS https://modelflare.dev/v1/chat/completions \ -H "Authorization: Bearer $MODELFLARE_API_KEY " \ -H "Content-Type: application/json" \ -d "{ \" model \": \" $MODEL_ID \", \" messages \": [ { \" role \": \" user \", \" content \": \" The customer was charged twice and wants a refund. \" } ], \" response_format \": { \" type \": \" json_schema \", \" json_schema \": { \" name \": \" support_ticket \", \" schema \": { \" type \": \" object \", \" properties \": { \" category \": { \" type \": \" string \", \" enum \": [ \" billing \", \" technical \", \" account \" ] }, \" priority \": { \" type \": \" integer \", \" minimum \": 1, \" maximum \": 3 }, \" requires_human \": { \" type \": \" boolean \" }, \" summary \": { \" type \": \" string \" } }, \" required \": [ \" category \", \" priority \", \" requires_human \", \" summary \" ], \" additionalProperties \": false }, \" strict \": true } } }" Enter fullscreen mode Exit fullscreen mode Bei Chat Completions wird der JSON-Text normalerweise in choices.message.content zurückgegeben. Parsen Sie den Inhalt, statt die Zeichenkette als bereits typisiertes Objekt zu behandeln.
Die beiden Varianten lassen sich wie folgt zusammenfassen:
Modelflares OpenAI-Kompatibilitätsschicht besitzt eine explizite Konvertierungslogik für diese beiden Varianten auf unterstützten OpenAI-/Codex-Routen. Diese Konvertierung erzeugt keine Structured-Outputs-Unterstützung in einem Upstream-Modell, dem diese fehlt. Für andere Modellfamilien, die als reines Chat-Completions-Durchleitungsformat bereitgestellt werden, bleibt der exakte Anfragevertrag des Upstream-Anbieters maßgeblich.
{ "category": "billing", "priority": 2, "requires_human": true, "summary": "Customer reports a duplicate charge and requests a refund." } Enter fullscreen mode Exit fullscreen mode Dieses Objekt erfüllt das Schema, aber die Anwendung sollte dennoch zwei getrennte Prüfungen durchführen.
Verwenden Sie einen JSON-Schema-Validator oder einen typisierten SDK-Helfer, um Folgendes zu bestätigen:
Auch wenn der Anbieter strikte Einhaltung zusichert, schützt die anwendungsseitige Validierung den Rest des Systems vor nicht unterstützten Routen, Integrationsfehlern, abgeschnittenen Inhalten und künftigen Vertragsänderungen.
Das Schema kann nicht bestimmen, ob der Kunde tatsächlich doppelt belastet wurde, ob Priorität 2 korrekt ist oder ob ein Mensch eine Rückerstattung genehmigen muss. Diese Entscheidungen erfordern Quelldaten und die Richtlinien der Anwendung.
Für geschäftskritische Workflows sollte man das Modell-Objekt als einen Klassifizierungsvorschlag behandeln. Vergleichen Sie es mit verlässlichen Datensätzen, setzen Sie Berechtigungen und finanzielle Grenzen in deterministischem Code durch und bewahren Sie eine sichere Audit-Referenz auf. Lassen Sie niemals ein strukturell gültiges Modell-Objekt eine Autorisierungs-, Abrechnungs- oder Sicherheitsgrenze umgehen.
Ein Produktions-Parser braucht mehr als einen JSON.parse-Aufruf für den Erfolgsfall.
Ein Modell kann eine Anfrage aus Sicherheitsgründen ablehnen. Der Structured-Output-Vertrag von OpenAI macht Ablehnungen von normaler Schema-Ausgabe unterscheidbar. Prüfen Sie den Antwortstatus und die Darstellung der Ablehnung, bevor Sie nach dem JSON-Objekt suchen; melden Sie eine Ablehnung nicht als Schema-Parsing-Fehler.
Wenn die Generierung stoppt, bevor das Objekt vollständig ist, kann kein Schema den fehlenden Teil reparieren. Prüfen Sie den Abschlussstatus und den Stop-Grund des Endpunkts. Setzen Sie ein Ausgabelimit, das für das größte gültige Objekt ausreichend ist, und halten Sie dabei Schema und angefragte Inhalte begrenzt.
Implementierungen strikter Ausgaben unterstützen häufig nur eine Teilmenge von JSON Schema. Beginnen Sie mit Objekten, Arrays, primitiven Typen, Enums, Pflichtfeldern und explizitem Verhalten bei zusätzlichen Eigenschaften. Prüfen Sie die aktuelle Anbieterdokumentation, bevor Sie Referenzen, rekursive Strukturen, komplexe Unions oder erweiterte Validierungs-Keywords verwenden.
Einige Anbieter verarbeiten ein neues Schema vor und cachen es. Die erste Anfrage mit einem neuen Schema kann daher länger dauern als spätere. Verwenden Sie stabile, versionierte Schemas wieder, statt für jede Anfrage ein einzigartiges Schema zu erzeugen, und trennen Sie Erstnutzungsmessungen von Latenzvergleichen im stationären Zustand.
Ein OpenAI-kompatibler Endpunkt kann gewöhnliche Chat Completions akzeptieren, während er json_schema ablehnt, strict ignoriert oder die Felder an ein Modell weiterleitet, das sie nicht unterstützt. Eine 200-Antwort mit JSON ist noch kein Beweis für strikte Einhaltung. Testen Sie ungültige und Grenzfälle und validieren Sie das resultierende Objekt.
Verwenden Sie eine kleine Matrix für jedes Modell und jede Route, die Sie aktivieren möchten.
Vergleichen Sie keine Modelle mit unterschiedlichen Schemas, Prompts, Regionen, Streaming-Modi oder Ausgabelimits und führen Sie das Ergebnis dann ausschließlich auf die Modellqualität zurück. Dokumentieren Sie das genaue Testdatum, da sich Anbieterverhalten und Modellverfügbarkeit ändern können.
Verwenden Sie Responses, wenn die Anwendung bereits mit typisierten Ausgabe-Items, Responses-Streaming-Events oder einem breiteren Responses-Tool-Workflow arbeitet. Verwenden Sie Chat Completions, wenn die Anwendung über eine stabile nachrichtenbasierte Integration verfügt und die gewählte Route deren response_format-Vertrag unterstützt. Die umfassendere Entscheidung wird in Responses API vs Chat Completions behandelt.
Für die erste Migration des Clients beginnen Sie mit dem OpenAI-Compatible API Guide. Für das Parsen von Events und unvollständigen Streams verwenden Sie den AI API Streaming Guide. Prüfen Sie Models & Pricing für die aktuelle Modell- und Group-Oberfläche und verifizieren Sie dann Structured Outputs auf genau der Route, die Ihr API-Schlüssel verwenden wird.
Structured Outputs entfalten ihren größten Nutzen, wenn sie die Antworten eines Modells auf eine verlässliche Anwendungsschnittstelle eingrenzen. Sie sind kein Ersatz für die Überprüfung von Fakten, für Autorisierungs- oder Abrechnungslogik, und "OpenAI-kompatibel" bleibt eine Behauptung, die man Funktion für Funktion prüfen muss, nicht ein Versprechen, das man allein aus der Base URL ableiten kann.
Hinweis: Ich bin Teil des Modelflare-Teams, das das in diesem Artikel erwähnte AI-API-Gateway entwickelt.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Posted on Oct 16 • Originally published at modelflare.dev
A field-level guide to strict JSON Schema outputs across Responses and Chat Completions, including validation layers, failure handling, and route compatibility tests.
Structured Outputs let an application request JSON that follows a defined schema instead of merely asking the model to “return JSON.” With an OpenAI-compatible API, the same schema can be used through Responses or Chat Completions, but the request fields are different—and compatibility must be verified for the selected model and route.
The safe production pattern has three layers: use strict schema output when the model supports it, parse and validate the returned JSON in the application, and then apply business rules to the values. Schema compliance removes many formatting failures; it does not prove that the model’s answer is factually or semantically correct.
There are three common ways to ask a model for JSON-like output.
The OpenAI Structured Outputs guide recommends Structured Outputs instead of JSON mode when the selected model and endpoint support it. The same guide also distinguishes two different jobs:
This article focuses on the first job. A later tool loop can reuse strict argument schemas, but its call IDs and result messages follow a different contract.
Consider a support-triage workflow. The application needs four fields:
{ "type": "object", "properties": { "category": { "type": "string", "enum": [ "billing", "technical", "account" ] }, "priority": { "type": "integer", "minimum": 1, "maximum": 3 }, "requires_human": { "type": "boolean" }, "summary": { "type": "string" } }, "required": [ "category", "priority", "requires_human", "summary" ], "additionalProperties": false } Enter fullscreen mode Exit fullscreen mode Making every property required and setting additionalProperties to false produces a stable object for downstream code. It does not make every possible JSON Schema keyword portable. OpenAI supports a documented subset, and another OpenAI-compatible provider may support a different subset or no strict schema mode at all.
The Responses API places the response schema under text.format. Set a Modelflare API key and choose a model that is currently listed for your API key and has been verified for Structured Outputs.
export MODELFLARE_API_KEY = "<YOUR_API_KEY>" export MODEL_ID = "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>" curl -sS https://modelflare.dev/v1/responses \ -H "Authorization: Bearer $MODELFLARE_API_KEY " \ -H "Content-Type: application/json" \ -d "{ \" model \": \" $MODEL_ID \", \" input \": \" The customer was charged twice and wants a refund. \", \" text \": { \" format \": { \" type \": \" json_schema \", \" name \": \" support_ticket \", \" schema \": { \" type \": \" object \", \" properties \": { \" category \": { \" type \": \" string \", \" enum \": [ \" billing \", \" technical \", \" account \" ] }, \" priority \": { \" type \": \" integer \", \" minimum \": 1, \" maximum \": 3 }, \" requires_human \": { \" type \": \" boolean \" }, \" summary \": { \" type \": \" string \" } }, \" required \": [ \" category \", \" priority \", \" requires_human \", \" summary \" ], \" additionalProperties \": false }, \" strict \": true } } }" Enter fullscreen mode Exit fullscreen mode In a Responses response, generated content appears in typed output items. SDKs may expose convenience helpers such as aggregated output text or parsed output, but an HTTP client should not assume that the structured object is a top-level field. Read the completed response object, locate its text output, then parse that text as JSON.
Chat Completions places the response schema under response_format.json_schema. The schema body is unchanged; only the wrapper differs.
curl -sS https://modelflare.dev/v1/chat/completions \ -H "Authorization: Bearer $MODELFLARE_API_KEY " \ -H "Content-Type: application/json" \ -d "{ \" model \": \" $MODEL_ID \", \" messages \": [ { \" role \": \" user \", \" content \": \" The customer was charged twice and wants a refund. \" } ], \" response_format \": { \" type \": \" json_schema \", \" json_schema \": { \" name \": \" support_ticket \", \" schema \": { \" type \": \" object \", \" properties \": { \" category \": { \" type \": \" string \", \" enum \": [ \" billing \", \" technical \", \" account \" ] }, \" priority \": { \" type \": \" integer \", \" minimum \": 1, \" maximum \": 3 }, \" requires_human \": { \" type \": \" boolean \" }, \" summary \": { \" type \": \" string \" } }, \" required \": [ \" category \", \" priority \", \" requires_human \", \" summary \" ], \" additionalProperties \": false }, \" strict \": true } } }" Enter fullscreen mode Exit fullscreen mode For Chat Completions, the JSON text is normally returned in choices.message.content. Parse the content rather than treating the string as an already typed object.
The two wrappers can be summarized as follows:
Modelflare’s OpenAI compatibility layer has explicit conversion logic for these two wrappers on supported OpenAI/Codex routes. That conversion does not create Structured Outputs support in an upstream model that lacks it. For other model families served as raw Chat Completions pass-through, the upstream provider’s exact request contract remains the source of truth.
{ "category": "billing", "priority": 2, "requires_human": true, "summary": "Customer reports a duplicate charge and requests a refund." } Enter fullscreen mode Exit fullscreen mode This object satisfies the schema, but the application should still perform two distinct checks.
Use a JSON Schema validator or a typed SDK helper to confirm:
Even when the provider promises strict adherence, application-side validation protects the rest of the system from unsupported routes, integration mistakes, truncated content, and future contract changes.
The schema cannot determine whether the customer was actually charged twice, whether priority 2 is correct, or whether a human must approve a refund. Those decisions require source data and application policy.
For high-impact workflows, treat the model object as a proposed classification. Compare it with authoritative records, enforce permissions and financial limits in deterministic code, and retain a safe audit reference. Never let a structurally valid model object bypass an authorization, billing, or security boundary.
A production parser needs more than a happy-path JSON.parse call.
A model may refuse a request for safety reasons. OpenAI’s structured-output contract makes refusals distinguishable from normal schema output. Check the response status and refusal representation before looking for the JSON object; do not report a refusal as a schema parsing error.
If generation stops before the object is complete, no schema can repair the missing suffix. Check the endpoint’s completion status and stop reason. Set an output limit that is sufficient for the largest valid object, while keeping the schema and requested content bounded.
Strict output implementations commonly support only a subset of JSON Schema. Start with objects, arrays, primitive types, enums, required fields, and explicit additional-property behavior. Check the current provider documentation before using references, recursive shapes, complex unions, or advanced validation keywords.
Some providers preprocess and cache a new schema. The first request for a new schema can therefore take longer than later requests. Reuse stable, versioned schemas instead of generating a unique schema for every request, and separate first-use measurements from steady-state latency comparisons.
An OpenAI-compatible endpoint can accept ordinary Chat Completions while rejecting json_schema, ignoring strict, or forwarding the fields to a model that does not support them. A 200 response containing JSON is still not proof of strict adherence. Test invalid and edge-case inputs and validate the resulting object.
Use a small matrix for every model and route you intend to enable.
Do not compare models with different schemas, prompts, regions, streaming modes, or output limits and then attribute the result only to model quality. Record the exact test date because provider behavior and model availability can change.
Use Responses when the application already works with typed output items, Responses streaming events, or a broader Responses tool workflow. Use Chat Completions when the application has a stable message-based integration and the selected route supports its response_format contract. The broader decision is covered in Responses API vs Chat Completions.
For initial client migration, start with the OpenAI-Compatible API Guide. For event parsing and incomplete streams, use the AI API Streaming Guide. Check Models & Pricing for the current model and group surface, then verify Structured Outputs on the exact route your API key will use.
Structured Outputs are most useful when they narrow a model response into a dependable application interface. They are not a substitute for factual verification, authorization, or billing logic—and “OpenAI-compatible” remains a claim to test feature by feature, not a guarantee to infer from the Base URL.
Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.