
Eine Anwendung, die ein KI-Modell verwendet, benötigt häufig präzise Daten: eine Kategorie, einen Betrag, eine Referenz oder bestimmte Felder zum Speichern.
Die Anweisung «Antworte nur mit JSON» reicht dafür nicht immer aus. Das Modell kann eine Einleitung ergänzen, eine Eigenschaft auslassen oder eine Kategorie wählen, die Ihre Anwendung nicht kennt.
Strukturierte Ausgaben, auch Structured Outputs genannt, ermöglichen mehr Kontrolle über diese Antworten. Wenn ein Modell und seine Route diese Funktion unterstützen, übermitteln Sie ein Schema, das die erwartete Struktur beschreibt.
Für zuverlässige Ergebnisse müssen drei Elemente zusammenwirken: ein präziser Datenvertrag, eine eingeschränkte Generierung und die Validierung in Ihrer Anwendung.
Dieser Leitfaden verwendet Modelle aus dem RouterLab-Katalog und zeigt ein vollständiges Beispiel zur Klassifizierung von Support-Tickets. Informationen und API-Schnittstellen wurden am 13. Oktober 2026 geprüft; Modellfähigkeiten können sich ändern.
1. Gültiges JSON, Schema-Konformität und korrekte Daten
Diese drei Eigenschaften sind unterschiedlich.
| Eigenschaft | Was sie überprüft |
|---|---|
| Gültiges JSON | Ein JSON-Parser kann den Text lesen. |
| Schema-Konformität | Felder, Typen und zulässige Werte entsprechen dem Datenvertrag. |
| Korrektheit | Die Daten geben den analysierten Inhalt richtig wieder. |
Ein Beispiel:
{
"categorie": "urgent",
"resume": "Un problème de paiement."
}
Dieses Objekt ist gültiges JSON. Es entspricht jedoch keinem Schema, das eine Kategorie aus facturation, technique, compte und autre sowie ein Feld priorite verlangt.
Umgekehrt kann ein Objekt vollständig dem Schema entsprechen und trotzdem ein Zahlungsproblem fälschlich der Kategorie technique zuordnen.
Strukturierte Ausgaben kontrollieren das Antwortformat. Die Qualität der Analyse muss separat bewertet werden. Bei OpenAI erklärt dies auch den Unterschied zwischen JSON-Modus und Structured Outputs: Der JSON-Modus gewährleistet gültige Syntax, Structured Outputs ergänzen die Einhaltung des unterstützten Schemas. OpenAI-Dokumentation.
2. RouterLab-Modelle und ihre veröffentlichten Routen verwenden
Die Beispiele in diesem Leitfaden verwenden folgende Kennungen:
| Modell | RouterLab-Kennung | Veröffentlichtes Anfrageformat |
|---|---|---|
| GPT-6 Sol | gpt-6-sol | OpenAI-kompatibel |
| Claude Sonnet 5.5 | claude-sonnet-5-5 | Claude Messages |
| Gemini 3.8 Flash | gemini-3.8-flash | OpenAI-kompatibel |
| Qwen 3.8 Flash | qwen3.8-flash | OpenAI-kompatibel |
| DeepSeek V4.1 Flash | deepseek-v4.1-flash | OpenAI-kompatibel |
Die verfügbaren Formate finden Sie im RouterLab-Katalog. Prüfen Sie, welche Modelle mit Ihrem Schlüssel zugänglich sind und welches Format die jeweilige Modellseite veröffentlicht.
Der Modellanbieter und das Protokoll Ihres Clients sind zwei separate Entscheidungen. Bei RouterLab kann ein Gemini-Modell über eine OpenAI-kompatible Route verwendet werden. Eine Claude-Messages-Route erwartet einen anderen Anfrageinhalt.
Protokollkompatibilität bedeutet nicht, dass jedes Modell sämtliche Parameter oder Schema-Einschränkungen akzeptiert.
3. Den Datenvertrag mit Zod oder Pydantic definieren
Zod ist eine Validierungsbibliothek für TypeScript. Pydantic übernimmt diese Aufgabe in Python.
Diese Bibliotheken definieren die erwarteten Daten und prüfen eingehende Ergebnisse. So bleiben Validierungsregeln an einem Ort, statt über die Anwendung verteilt zu sein.
Unser Support-Ticket enthält vier Felder:
categorie:facturation,technique,compteoderautre, also Abrechnung, Technik, Konto oder Sonstiges;priorite:basse,normaleoderhaute, also niedrig, normal oder hoch;resume: eine kurze Beschreibung des Problems;reference: eine im Text vorhandene Referenz odernull.
Alle Sprachversionen behalten dieselben französischen Feldnamen, Enum-Werte und dasselbe Beispiel-Ticket bei. Damit verwenden sie einen gemeinsamen Datenvertrag und dieselbe Eingabe für den Vergleich.
Die letzte Regel ist wichtig. Wenn ein Kunde keine Referenz angibt, muss das Modell dieses Fehlen ausdrücken können. Eine in jedem Fall verlangte Zeichenkette kann einen erfundenen Wert begünstigen.
Die Anweisungen sollten auch die Klassifizierungsregeln festlegen. Eine ausstehende Zahlung bedeutet nicht automatisch hohe Priorität. Diese kann beispielsweise auf blockierende Vorfälle oder ausdrücklich genannte Fristen beschränkt werden.
4. Ein vollständiges TypeScript-Beispiel
Wir verwenden die OpenAI-kompatible RouterLab-Route:
POST https://api.routerlab.ch/v1/chat/completions
Das OpenAI-SDK unterstützt eine eigene Basis-URL. Die Authentifizierung erfolgt mit Ihrem RouterLab-Schlüssel. RouterLab-Integrationsleitfaden.
Abhängigkeiten installieren
In einem Node.js-Projekt:
npm install openai zod
npm install --save-dev tsx @types/node
Hinterlegen Sie anschliessend den Schlüssel in der Serverumgebung.
Unter Linux oder macOS:
export ROUTERLAB_API_KEY="ihr-routerlab-schluessel"
In PowerShell:
$env:ROUTERLAB_API_KEY = "ihr-routerlab-schluessel"
So prüfen Sie die mit diesem Schlüssel verfügbaren Kennungen:
curl https://api.routerlab.ch/v1/models \
-H "Authorization: Bearer $ROUTERLAB_API_KEY"
Anfrage senden und Antwort validieren
Erstellen Sie die Datei classer-ticket.ts:
import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const TicketSchema = z.strictObject({
categorie: z.enum(["facturation", "technique", "compte", "autre"]),
priorite: z.enum(["basse", "normale", "haute"]),
resume: z.string(),
reference: z.string().nullable(),
});
const responseFormat = zodResponseFormat(TicketSchema, "ticket_support");
async function main() {
const apiKey = process.env.ROUTERLAB_API_KEY;
if (!apiKey) throw new Error("ROUTERLAB_API_KEY is missing.");
const client = new OpenAI({
apiKey,
baseURL: "https://api.routerlab.ch/v1",
timeout: 60_000,
maxRetries: 0,
});
const model = process.env.ROUTERLAB_MODEL ?? "gpt-6-sol";
const start = performance.now();
const response = await client.chat.completions.create({
model,
stream: false,
response_format: responseFormat,
messages: [
{
role: "system",
content: [
"Classe le ticket de support fourni.",
"Utilise autre si la catégorie est ambiguë.",
"La priorité normale est la valeur habituelle.",
"Réserve haute à un incident bloquant ou urgent",
"explicitement décrit dans le message.",
"Résume le problème en français.",
"Copie la référence seulement si elle est présente.",
"Sinon, utilise null.",
].join(" "),
},
{
role: "user",
content:
"Mon paiement a été accepté, mais mes crédits " +
"ne sont toujours pas disponibles.",
},
],
});
const choice = response.choices[0];
if (!choice) throw new Error("No completion was returned.");
if (choice.message.refusal) throw new Error("The model refused the request.");
if (choice.finish_reason === "length") {
throw new Error("The response was truncated.");
}
if (choice.finish_reason !== "stop") {
throw new Error(`Unexpected finish reason: ${choice.finish_reason}`);
}
const content = choice.message.content;
if (!content) throw new Error("The response is empty.");
let data: unknown;
try {
data = JSON.parse(content);
} catch {
throw new Error("The response is not valid JSON.");
}
const validation = TicketSchema.safeParse(data);
if (!validation.success) {
console.error(validation.error.issues);
throw new Error("The response does not match the schema.");
}
console.log(JSON.stringify(validation.data, null, 2));
console.error({
model: response.model,
request: response.id,
duration_ms: Math.round(performance.now() - start),
usage: response.usage,
});
}
main().catch((error) => {
if (error instanceof OpenAI.APIError) {
console.error("API error:", error.status, error.message);
} else {
console.error(error instanceof Error ? error.message : "Unexpected error.");
}
process.exitCode = 1;
});
Führen Sie das Skript aus:
npx tsx classer-ticket.ts
Eine beispielhafte Antwort, die dem Vertrag entspricht:
{
"categorie": "facturation",
"priorite": "normale",
"resume": "Le paiement est accepté, mais les crédits ne sont pas disponibles.",
"reference": null
}
Die Formulierung der Zusammenfassung kann variieren. Kategorien und erwartete Struktur bleiben durch das Schema festgelegt.
Der Helper zodResponseFormat erzeugt das Schemaformat für die Anfrage. Die lokale Validierung verwendet safeParse, das einen Erfolg oder einen Fehler explizit zurückgibt. z.strictObject weist zudem unerwartete Eigenschaften zurück. Validierung mit Zod, strikte Objekte.
Einschränkungen des Strict-Modus
Im OpenAI-Format fordert json_schema mit strict: true die Einhaltung des Schemas an. Objekte müssen insbesondere additionalProperties: false festlegen, und ihre Eigenschaften müssen erforderlich sein. Ein Typ, der null akzeptiert, kann einen unbekannten Wert darstellen.
Nicht alle JSON-Schema-Schlüsselwörter werden unterstützt. Ein komplexes fachliches Schema kann eine vereinfachte Version für den Anbieter benötigen. Der SDK-Helper ergänzt keine fehlende Fähigkeit im Modell oder in der Route. Von OpenAI unterstützte Schemas.
5. Das Beispiel mit Gemini oder Qwen wiederverwenden
Für eine andere OpenAI-kompatible Route bleibt der Aufbau des Skripts gleich. Ändern Sie die Modellkennung:
export ROUTERLAB_MODEL="gemini-3.8-flash"
npx tsx classer-ticket.ts
Oder:
export ROUTERLAB_MODEL="qwen3.8-flash"
npx tsx classer-ticket.ts
In PowerShell verwenden Sie beispielsweise $env:ROUTERLAB_MODEL = "gemini-3.8-flash" und starten das Skript erneut.
Diese Versuche überprüfen die Unterstützung der strukturierten Anfrage auf der gewählten Route sowie die Qualität der Klassifizierung.
Eine HTTP-200-Antwort allein belegt keine Schema-Garantie. Behalten Sie die lokale Validierung bei und prüfen Sie unterschiedliche Eingaben.
Gemini 3.8 Flash unterstützt strukturierte Ausgaben in seiner nativen API. Beim Zugriff über RouterLab gilt hier das Anfrageformat der OpenAI-kompatiblen Route. Offizielle Modellseite von Gemini 3.8 Flash.
6. Claude Sonnet 5.5 verwendet ein anderes Format
Claude Sonnet 5.5 verwendet hier die veröffentlichte Claude-Messages-Route:
POST https://api.routerlab.ch/v1/messages
Diese nutzt die Header x-api-key und anthropic-version. Anfrageinhalt und Antwortblöcke unterscheiden sich von Chat Completions. RouterLab-Leitfaden für Claude Messages.
Für native JSON-Ausgaben von Claude wird das Schema über output_config.format übermittelt. Mit dem zuvor definierten responseFormat folgt eine strukturierte Anfrage diesem Aufbau:
const corpsClaude = {
model: "claude-sonnet-5-5",
max_tokens: 4096,
system:
"Classe le ticket. Utilise une priorité normale " +
"sauf urgence explicite. Une référence absente vaut null.",
messages: [
{
role: "user",
content:
"Mon paiement a été accepté, mais mes crédits " +
"ne sont toujours pas disponibles.",
},
],
output_config: {
format: {
type: "json_schema",
schema: responseFormat.json_schema.schema,
},
},
};
Dieser Ausschnitt beschreibt das native Format, das auf der verwendeten Route zu prüfen ist. Bestätigen Sie die Unterstützung von output_config.format, bevor Sie es in Ihre Anwendung integrieren.
Prüfen Sie bei der Antwort den stop_reason, lesen Sie den Textblock aus und wenden Sie dasselbe lokale Validierungsschema an.
Native JSON-Ausgaben und strikte Tool-Argumente sind bei Claude separate Funktionen. Ein erzwungener Tool-Aufruf ersetzt den Strict-Modus nicht. Claude Sonnet 5.5 und Opus 5.5 weisen tool_choice-Konfigurationen vom Typ tool oder any zurück. Strukturierte Ausgaben von Claude, Einschränkungen für Tool-Aufrufe.
7. Das Ergebnis in Python mit Pydantic validieren
Eine Python-Anwendung kann denselben Vertrag mit Pydantic anwenden.
Installieren Sie die Bibliothek:
python -m pip install pydantic
Speichern Sie in einem Linux- oder macOS-Terminal das vom TypeScript-Skript erzeugte JSON:
npx tsx classer-ticket.ts > ticket.json
Diagnosemeldungen bleiben getrennt, da sie auf stderr ausgegeben werden. Achten Sie unter Windows darauf, ticket.json als UTF-8 zu speichern.
Erstellen Sie valider-ticket.py:
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict
class Ticket(BaseModel):
model_config = ConfigDict(extra="forbid")
categorie: Literal["facturation", "technique", "compte", "autre"]
priorite: Literal["basse", "normale", "haute"]
resume: str
reference: str | None
content = Path("ticket.json").read_text(encoding="utf-8")
ticket = Ticket.model_validate_json(content, strict=True)
print(ticket.model_dump())
Führen Sie das Skript anschliessend aus:
python valider-ticket.py
Das Feld reference muss auch dann vorhanden sein, wenn sein Wert null ist. Zusätzliche Eigenschaften werden zurückgewiesen. Ein Strukturfehler löst eine Validierungsausnahme aus. JSON lesen und validieren mit Pydantic.
8. Fehler behandeln, ohne unvollständige Daten zu speichern
Unterschiedliche Fehler erfordern unterschiedliche Massnahmen.
| Situation | Geeignete Massnahme |
|---|---|
| Schema oder Parameter zurückgewiesen | Kompatibilität zwischen Modell, Route und Schema prüfen. |
| Verweigerung durch das Modell | Die Verweigerung als separates Ergebnis behandeln. |
| Abgebrochene Antwort | Generierungsgrenzen und Umfang der Aufgabe prüfen. |
| Ungültiges JSON | Ergebnis verwerfen und die Ursache der fehlenden Vertragseinhaltung untersuchen. |
| Schema-Verletzung | Validierungsfehler protokollieren und Ergebnis verwerfen. |
| Schema-konforme, aber falsche Daten | Anweisungen, Beispiele und Bewertungskriterien verbessern. |
Ein schemaabhängiger 400-Fehler wird durch identische Wiederholungen nicht behoben. Ein vorübergehender Ausfall kann einige zeitlich versetzte Wiederholungen rechtfertigen.
Legen Sie eine maximale Anzahl von Versuchen fest und messen Sie deren Kosten. Das Beispiel deaktiviert automatische Wiederholungen, damit jede Ausführung einem sichtbaren Versuch entspricht.
Beim Streaming können eingehende Fragmente unvollständig sein. Warten Sie auf das Ende der Antwort und die Validierung, bevor Sie das endgültige Objekt speichern.
9. Auch die inhaltliche Korrektheit prüfen
Die Schema-Validierung reicht nicht aus, um eine korrekte Ticket-Klassifizierung festzustellen.
Bereiten Sie Beispiele mit erwarteten Antworten vor:
- eine akzeptierte Zahlung mit fehlenden Credits;
- einen zurückgewiesenen API-Schlüssel;
- eine Anfrage zur Änderung der E-Mail-Adresse;
- eine Nachricht, die für eine Kategorie zu ungenau ist;
- einen ausdrücklich beschriebenen dringenden Vorfall;
- eine Nachricht mit einer zu übernehmenden Referenz.
Messen Sie für jedes Modell Schema-Konformität und Korrektheit separat. Eine zulässige, aber falsch gewählte Kategorie ist ein fachlicher Fehler, selbst wenn das JSON fehlerfrei ist.
Extrahierte Felder müssen ebenfalls mit der Quelle abgeglichen werden. Eine Referenz im korrekten Format kann trotzdem erfunden sein.
10. Die Kosten eines akzeptierten Ergebnisses vergleichen
Ein sinnvoller Vergleich verwendet dieselben Tickets, Regeln und Akzeptanzkriterien.
Erfassen Sie:
- den Anteil schema-konformer Ergebnisse;
- den Anteil korrekter Klassifizierungen;
- Verweigerungen und Abbrüche;
- die Latenz;
- die Anzahl der Wiederholungen;
- die abgerechneten Kosten für ein akzeptiertes Ergebnis.
Vergleichen Sie beispielsweise GPT-6 Sol und Gemini 3.8 Flash mit demselben Skript, danach Claude Sonnet 5.5 mit dem passenden Protokoll. Dieser Leitfaden beschreibt eine Vergleichsmethode. Die gezeigten Antworten sind Beispiele und stellen keinen Modell-Benchmark dar.
Strukturierte Ausgaben garantieren keine identische Rechnung gegenüber einer freien Antwort. Claude ergänzt Systemanweisungen, die als Eingabe-Tokens abgerechnet werden. Die erste Verwendung eines Schemas kann bei OpenAI zusätzliche Vorbereitungslatenz verursachen. Claude-Dokumentation, OpenAI-Dokumentation.
Massgeblich sind die Kosten eines korrekten, akzeptierten Ergebnisses, einschliesslich der dafür benötigten Wiederholungen.
Vor dem Produktionseinsatz
- Definieren Sie den Vertrag und unbekannte Werte im Code.
- Prüfen Sie Kennung, Protokoll und Fähigkeiten des RouterLab-Modells.
- Bestätigen Sie die Schema-Unterstützung auf der gewählten Route.
- Behandeln Sie Verweigerungen, Abbrüche und Validierungsfehler.
- Prüfen Sie fachliche Regeln vor dem Speichern.
- Bewerten Sie mehrere Modelle anhand repräsentativer Eingaben.
- Messen Sie Qualität, Latenz und Gesamtkosten des Ablaufs.
Testen Sie die RouterLab-API
Gehen Sie von einem Artikel zu einer tatsächlichen Abfrage über: Erstellen Sie eine Testversion, rufen Sie einen Schlüssel ab und rufen Sie Modelle über eine OpenAI-kompatible API auf.