AIREITER

OpenRouter Structured Output: Warum dein Schema ignoriert wird

Zuletzt aktualisiert: 2026-08-23 01:24:37

Ein JSON Schema liefert bei einem OpenRouter-Modell saubere, typisierte Daten – beim nächsten kommen mit exakt demselben Request plötzlich andere Keys, ein leerer String oder ein 400-Fehler zurück. Reddit-Nutzer u/MicBeckie testete Qwen-Modelle über OpenRouters Structured Outputs und bekam nach eigener Aussage „9 von 10 Mal immer Fehler“. OpenAI-Modelle hielten das Schema im selben Setup dagegen ein.

Das ist kein einzelner Bug, den man einfach melden kann. Bei OpenRouter gilt Structured-Output-Support pro Endpoint, nicht pro Modell. Und „Support“ kann drei sehr unterschiedliche Durchsetzungsstufen bedeuten: von nativ erzwungener Schema-Validierung bis zu Providern, die das Schema eher als Empfehlung behandeln. Dieser Guide erklärt das Routing, sechs typische Fehlerbilder und die Maßnahmen, mit denen sich Schema-Ausgaben produktionsreif absichern lassen. Die Mechanik der Durchsetzung basiert auf der offiziellen Dokumentation zu Structured Outputs; die Beispiele für Fehler stammen aus den jeweils verlinkten Entwickler-Threads.

Dokumentationsseite zu OpenRouter Structured Outputs

Was „Structured-Output-Support“ bei OpenRouter wirklich bedeutet

OpenRouter akzeptiert einen response_format-Parameter mit type: "json_schema", einem Schema-name, dem Flag strict und dem eigentlichen JSON Schema. Ein minimaler Request sieht so aus:

{
  "model": "openai/gpt-4o",
  "messages": [{ "role": "user", "content": "Extract the shipping info" }],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "shipping_info",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "tracking_number": { "type": "string", "description": "Carrier tracking ID" },
          "carrier": { "type": "string" },
          "eta_days": { "type": "number", "description": "Days until delivery" }
        },
        "required": ["tracking_number", "carrier", "eta_days"],
        "additionalProperties": false
      }
    }
  }
}

Zwei Hinweise aus der offiziellen Doku entscheiden darüber, ob das überhaupt funktioniert:

  • Der Support hängt am Endpoint, nicht am Modell. Wird ein Modell von fünf Providern bereitgestellt, können Structured Outputs bei zwei davon funktionieren und bei den anderen nicht. Im Bereich Providers auf der Modellseite steht der Parameter structured_outputs für jeden Provider. Die Doku weist außerdem darauf hin, dass sich „endpoint support can also change over time“.
  • Die Abdeckung wuchs von einer kleinen Basis aus. OpenRouter kündigte Structured Outputs am 12. Dezember 2024 zunächst nur für OpenAI 4o- und Fireworks-Modelle an. Weitere Anbieter kamen anschließend einzeln hinzu. Jede heute festgehaltene Modellliste veraltet daher schnell.

Die Dokumentation empfiehlt außerdem Beschreibungen für jede Property und additionalProperties: false. Gerade auf niedrigeren Durchsetzungsstufen dient das Schema zugleich als Prompt-Kontext für das Modell.

Ein Flag, drei Stufen der Schema-Durchsetzung

strict: true hat je nach Ziel-Endpoint eine andere Bedeutung. Die offizielle Anleitung unterscheidet drei Provider-Stufen:

StufeSo verarbeitet der Provider dein SchemaIst das Ergebnis verlässlich?
Nativer Strict ModeSetzt das Schema beim Decoding exakt durchJa: Die Ausgabe entspricht konstruktionsbedingt dem Schema
Übersetztes FormatWandelt dein Schema in ein providerspezifisches Structured-Output-Format umWeitgehend: begrenzt auf die Schema-Features dieses Formats
Starker HinweisGibt dem Modell das Schema als Anleitung mitNein: An guten Tagen schemaähnlich, an schlechten mit halluzinierten Keys

OpenRouter zeigt beim Request nicht an, welche Stufe ein bestimmter Endpoint nutzt; die Doku verweist dafür auf die Unterlagen des jeweiligen Providers. Selbst native Strict Modes akzeptieren zudem nur bestimmte JSON-Schema-Features. Ungewöhnliche Keywords können deshalb auf besonders strikten Endpoints scheitern, während sie anderswo nur als Hinweis weitergereicht werden.

Für Claude dokumentiert die Seite zum Provider-Routing einen Sonderfall: Bei response_format.type: "json_schema" setzt OpenRouter automatisch Anthropics Beta-Header structured-outputs-2025-11-13. Damit werden strikte, schema-validierte Tool-Argumente aktiviert. Bei Tool-Definitionen mit strict: true, die über tools übergeben werden, muss der Aufrufer diesen Beta-Header jedoch selbst mitsenden. Andernfalls entfernt OpenRouter strict und routet die Anfrage ohne das Flag. Das Problem bleibt still: Tool Calls werden nicht mehr gegen das Schema validiert, ohne dass ein Fehler erscheint.

Sechs typische Wege, wie dasselbe Schema scheitert

Zwei Fehlerklassen schlagen direkt fehl und sind in der offiziellen Anleitung dokumentiert. Vier weitere tauchen in Community-Threads auf – und genau sie kosten in der Praxis oft einen Nachmittag.

Sofortiger Fehler 1: Der Endpoint unterstützt Structured Outputs nicht. Die Anfrage endet mit einer Fehlermeldung zur nicht unterstützten Fähigkeit. Ärgerlich, aber eindeutig. Sofortiger Fehler 2: Das JSON Schema ist ungültig. Die API lehnt den Request ab, weil das Schema nicht geparst werden kann oder gegen die Schema-Regeln des Endpoints verstößt.

Stiller Fehler 1: Das Schema wird ignoriert. Die Antwort ist valides JSON, passt aber zu einem völlig anderen Schema. Im Thread über nicht eingehaltene Schemas auf r/LocalLLaMA schreibt u/DaniyarQQQ:

Es gibt JSON zurück, das überhaupt nicht wie mein Schema aussieht.

u/MicBeckie beschreibt im selben Thread das Diagnoseproblem:

Entweder sehe ich Erfolg, wenn das JSON exakt den Anforderungen entspricht, oder ich bekomme einen Fehler, ohne das JSON sehen zu können.

Wrapper-Fehler 2: Ein 400 zu tool_choice, das du nie gesendet hast. Im verlinkten LangChainJS-Fall implementierte withStructuredOutput() „Structured Output“, indem es tool_choice auf eine generierte Funktion zwang. Bei Modellen, die Tool Calls anbieten, aber kein erzwungenes Tool Choice unterstützen, endet der Request mit invalid_request_error. Im DeepSeek-v4-Fall nannte die Meldung das Modell direkt: deepseek-reasoner does not support this tool_choice. u/shansoft stieß über LangChainJS genau auf dieses Problem (Thread). Die Annahme von u/eyueldk – „it says it supports tool calls, thus should support structured output“ – erwies sich als falsch. Tool-Call-Support und strikter Schema-Support sind getrennte Fähigkeiten.

Stiller Fehler 3: Kein Fehler, aber auch kein Inhalt. Ein Bericht zu gpt-oss-120b beschreibt einen Strict-Schema-Request, der über eine direkte Provider-Route einen 400-Fehler auslöst, über OpenRouter jedoch mit 200 und leerem message.content zurückkommt. In einem weiteren Thread auf r/openrouter gibt ein angeblich unterstütztes Modell nur [1] oder [1.1] zurück. Ein SDK, das einen leeren String problemlos verarbeitet, verschiebt den Fehler um drei Schichten nach hinten.

Stiller Fehler 4: Der Endpoint hängt. u/Beneficial-Loss-1031 berichtet über Endpoints, die Structured Output für DeepSeek v4 zwar auswiesen (Thread):

deepinfra/fp4 und akashml/fp8 haben eine Structured-Output-Option, aber ich habe bei beiden 3 Minuten auf die API-Antwort gewartet und nichts erhalten.

#FehlerbildWas sichtbar wirdTypische Ursache
1Nicht unterstützter EndpointFehler: Structured Outputs werden nicht unterstütztRouting zu einem Provider ohne diese Fähigkeit
2Ungültiges SchemaAPI-Fehler beim RequestDas Schema verletzt die Regeln des Endpoints
3Schema ignoriertValides JSON, aber falsche KeysDurchsetzung auf Hinweis-Stufe
4tool_choice-400invalid_request_errorSDK bildet das Schema über einen erzwungenen Tool Call nach
5Leerer Inhalt200, leeres message.contentProvider verarbeitet Strict Mode fehlerhaft
6HängerMinutenlang keine AntwortIm Bericht nicht bestätigt – 3 Minuten Wartezeit auf fp4/fp8-Endpoints

Den Request absichern, bevor du das Modell beschuldigst

Die Einstellung mit dem größten Hebel ist require_parameters: true im provider-Objekt. Standardmäßig steht sie auf false; unbekannte Parameter werden dann an Provider durchgereicht, die sie womöglich still ignorieren. Selbst bei false sind response_format und Structured Outputs bei der Endpoint-Auswahl nur eine weiche Präferenz: erwünscht, aber nicht garantiert. Mit true beschränkt OpenRouter das Routing laut Dokumentation zum Provider-Routing auf Endpoints, die jeden gesendeten Parameter unterstützen:

{
  "model": "deepseek/deepseek-chat",
  "messages": [{ "role": "user", "content": "Extract the shipping info" }],
  "response_format": { "type": "json_schema", "json_schema": { "name": "shipping_info", "strict": true, "schema": { "...": "..." } } },
  "provider": {
    "require_parameters": true,
    "order": ["fireworks"],
    "allow_fallbacks": false
  }
}

Jede Einschränkung verkleinert den Pool möglicher Provider. Mit allow_fallbacks: false tauschst du Verfügbarkeit gegen Determinismus. Dieselbe Routing-Doku beschreibt die Standardstrategie als Load Balancing anhand von Uptime und dem Kehrwert des Preisquadrats über die vorangegangenen 30 Sekunden. Das optimiert auf günstig und gesund, nicht auf schemafähig. Mit einem einzelnen Provider in order und deaktivierten Fallbacks wird das Routing reproduzierbar: Bei einer Störung kann der Request nicht auf einen anderen Provider ausweichen. Welche Durchsetzungsstufe dieser Endpoint bietet, musst du trotzdem selbst prüfen.

Zwei Prüfroutinen erfassen, was Routing allein nicht lösen kann:

  • Prüfe, welcher Provider den Request bedient hat. Die Generation-Metadaten von OpenRouter zeigen das Provider-Routing pro Generation – zusätzlich zu Modell, Latenz und Token-Zahlen. Wenn die Ausgabequalität schwankt, erkennst du so, ob sich das Modellverhalten geändert hat oder der Router den Provider.
  • Validiere immer clientseitig. Keine der drei Stufen ersetzt einen Pydantic- oder Zod-Parse auf deiner Seite. Die wiederkehrende Erkenntnis aus den r/LLMDevs-Testthreads: „valides JSON“, „schema-valide“ und „semantisch korrekt“ sind drei verschiedene Anforderungen. Nur die ersten beiden liegen überhaupt teilweise im Verantwortungsbereich der API.

Streaming funktioniert – das Parsing ist deine Aufgabe

Structured Outputs lassen sich mit stream: true kombinieren. Laut Doku streamt das Modell dabei gültiges partielles JSON; nach Abschluss des Streams soll die zusammengesetzte Antwort dem Schema entsprechen. Diese Zusage hängt jedoch weiterhin von der Durchsetzungsstufe des Endpoints ab. Ein Endpoint auf Hinweis-Stufe kann also noch immer eine nicht konforme Ausgabe zusammensetzen. Das finale Objekt musst du selbst validieren. Einen inkrementellen Parser liefert die Doku ebenfalls nicht – für latenzsensitive UIs liegt genau dort die eigentliche technische Arbeit. Aus dem Thread zu Streaming-Best-Practices auf r/LLMDevs:

Am Ende habe ich einfach eine Funktion geschrieben, die das JSON selbst vervollständigt. — u/am174744

„… das ist tatsächlich eine Zustandsmaschine.“ — u/ImNotLegitLol, als Korrektur der Vorstellung, man müsse nur reparieren und dann parsen

Praktische Optionen: Partielles JSON mit einem streamingtoleranten Parser verarbeiten, nur vollständig eingetroffene Felder rendern oder auf inkrementelles Rendering verzichten und bis zum fertigen Objekt einen Ladeindikator anzeigen.

Was Response Healing repariert – und was nicht

Das Plugin Response Healing von OpenRouter richtet sich an nicht-streamende json_schema-Requests und korrigiert unvollständige Formatierungen: abgeschnittenes JSON, überflüssige Markdown-Codeblöcke und ähnliche Probleme. Wichtiger als die reparierten Fälle sind zwei klare Grenzen:

  1. Streaming ist ausgeschlossen. Laut Doku gilt das Plugin nur für nicht-streamende Requests.
  2. Schema-Verstöße bleiben bestehen. Healing macht JSON parsebar. Eine Antwort, die dein Schema ignoriert hat, wird dadurch nicht schema-konform. Fehlerbild 3 von oben bleibt also unverändert.

Modelle auswählen, die Schemas tatsächlich einhalten

Modelllisten veralten, sinnvolle Auswahlkriterien nicht. Mit drei Filtern lassen sich die meisten der genannten Fehlerbilder abfangen:

  1. Native strikte Durchsetzung. Bevorzuge Modelle, deren Serving-Provider Schemas beim Decoding erzwingt, statt sie zu übersetzen oder nur als Hinweis einzubringen. Die Providers-Tabelle auf der Modellseite zeigt, welche Endpoints structured_outputs ausweisen; die Provider-Stufe bestimmt jedoch die Qualität der Durchsetzung.
  2. Ein nachvollziehbarer Provider. Vergleiche die Provider-Zuordnung über mehrere Aufrufe mit einem bekannten, funktionierenden Endpoint. Verteilt der Router Requests auf Provider unterschiedlicher Stufen, ist deine Fehlerrate eine Routing-Lotterie. Pinne den Provider oder wähle ein Modell mit nur einem Provider.
  3. Ein selbst ausgeführter Smoke-Test statt eines gelesenen Erfahrungsberichts. Community-Signale altern schnell – in beide Richtungen. Sowohl die Qwen-Fehlerberichte oben als auch der fehlende Support von DeepSeek v4 können sich ändern, wenn Provider ihre Endpoints aktualisieren. Entscheidend ist allein die Zuverlässigkeit, die dein eigenes Schema liefert.

FAQ zu OpenRouter Structured Outputs

Was ist der Unterschied zwischen json_object und json_schema?

json_object fordert nur syntaktisch valides JSON an. json_schema liefert zusätzlich ein Schema, dem die Antwort entsprechen muss. json_object garantiert also JSON-Syntax, nicht die Einhaltung deines Schemas auf Feldebene. Wenn nachgelagerter Code benannte Felder erwartet, musst du selbst validieren.

Welche OpenRouter-Modelle unterstützen Structured Outputs?

Eine statische Liste ist nicht verlässlich: Support gilt pro Endpoint, verändert sich mit der Zeit und begann im Dezember 2024 ausschließlich mit OpenAI 4o- und Fireworks-Modellen. Prüfe auf der Modellseite im Bereich Providers für jeden Endpoint das Flag structured_outputs.

Warum ignoriert das Modell mein Schema?

Drei häufige Ursachen: Der Request wurde zu einem Endpoint auf Hinweis-Stufe oder ohne Support geroutet – behebe das mit require_parameters: true und einem festgelegten Provider. Oder das Schema verwendet Keywords, die der Strict Mode des Endpoints ablehnt. Möglich ist auch, dass ein SDK-Wrapper Structured Output über Tool Calling nachbildet, obwohl das Modell kein erzwungenes Tool Choice unterstützt.

Kann ich Pydantic oder LangChain mit OpenRouter Structured Outputs verwenden?

Ja. Die offizielle Doku beschreibt das Request-Format als kompatibel mit der Chat-Completions-ähnlichen API von OpenRouter. Pydantic-generierte Schemas und das OpenAI SDK funktionieren daher direkt. Auch LangChains withStructuredOutput() funktioniert, aber prüfe, ob tatsächlich response_format gesendet wird und nicht über tool_choice emuliert wird. Letzteres verursachte die 400-Fehler bei DeepSeek v4.

Funktioniert Structured Output mit Streaming?

Ja. Der Stream liefert valides partielles JSON, aber die endgültige Schema-Konformität hängt von der Durchsetzungsstufe des Endpoints ab. Validere daher das zusammengesetzte Objekt selbst. Das inkrementelle Parsing der Fragmente ist Aufgabe deiner Anwendung, und Response Healing greift nicht bei Streams.

Validiert OpenRouter Antworten gegen mein Schema?

Nicht garantiert über alle Endpoints hinweg: Die Durchsetzung hängt von der Provider-Stufe ab, und Response Healing repariert nur fehlerhaftes JSON, keine Schema-Verstöße. Clientseitige Validierung bleibt Pflicht.

Der Smoke-Test mit 10 Aufrufen

Bevor ein Modell mit Structured Outputs in Produktion geht, solltest du diesen Test ausführen:

  1. Lege ein repräsentatives Schema fest: mittlere Komplexität, additionalProperties: false und Beschreibungen für alle Properties.
  2. Sende 10 identische Requests mit strict: true und require_parameters: true; Fallbacks bleiben aktiviert. Dieser Durchlauf testet bewusst das Fallback-Verhalten, also schalte sie nicht ab.
  3. Bewerte jede Antwort nach drei Kriterien: Parsebares JSON? Schema-valide? Semantisch plausibel?
  4. Halte über die Generation-Metadaten fest, welcher Provider jede Antwort ausgeliefert hat. Eine Quote von 10/10, verteilt auf vier verschiedene Provider, ist eine Routing-Lotterie und keine Garantie.
  5. Triff eine Entscheidung: unverändert ausrollen, provider.order auf den erfolgreichen Endpoint pinnen und die 10 Aufrufe erneut mit festem Provider ausführen – oder das Modell wechseln und eine clientseitige Validierungs- und Retry-Schicht ergänzen.

Welchen Grenzwert du akzeptierst, entscheidest du selbst. Bei weniger als 9/10 für ein festes Schema sind Retries und Validierungscode aber keine Option mehr. Sie sind Teil des Produkts.

Weiterführend: wie der Auto Router von OpenRouter Provider auswählt, Kosten mit OpenRouter Prompt Caching senken und OpenRouter-429-Rate-Limits beheben.