Zurück zum Blog
IntegrationMittelstufeTechnischer Artikel

Zuverlässiges JSON mit RouterLab: Schema und Validierung

Veröffentlicht 13 Okt. 202611 Min. gelesenRouterLab

Zusammenfassung der Entscheidung

Ein praktischer Leitfaden, um einen JSON-Vertrag zu definieren, strukturierte Ausgaben von RouterLab-Modellen anzufordern und Ergebnisse vor der Verwendung zu validieren.

APIJSONStructured OutputsTypeScriptPythonRouterLab
Zuverlässiges JSON mit RouterLab: Schema und Validierung

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.

EigenschaftWas sie überprüft
Gültiges JSONEin JSON-Parser kann den Text lesen.
Schema-KonformitätFelder, Typen und zulässige Werte entsprechen dem Datenvertrag.
KorrektheitDie Daten geben den analysierten Inhalt richtig wieder.

Ein Beispiel:

json
RouterLab
{
  "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:

ModellRouterLab-KennungVeröffentlichtes Anfrageformat
GPT-6 Solgpt-6-solOpenAI-kompatibel
Claude Sonnet 5.5claude-sonnet-5-5Claude Messages
Gemini 3.8 Flashgemini-3.8-flashOpenAI-kompatibel
Qwen 3.8 Flashqwen3.8-flashOpenAI-kompatibel
DeepSeek V4.1 Flashdeepseek-v4.1-flashOpenAI-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, compte oder autre, also Abrechnung, Technik, Konto oder Sonstiges;
  • priorite: basse, normale oder haute, also niedrig, normal oder hoch;
  • resume: eine kurze Beschreibung des Problems;
  • reference: eine im Text vorhandene Referenz oder null.

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:

text
RouterLab
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:

bash
RouterLab
npm install openai zod
npm install --save-dev tsx @types/node

Hinterlegen Sie anschliessend den Schlüssel in der Serverumgebung.

Unter Linux oder macOS:

bash
RouterLab
export ROUTERLAB_API_KEY="ihr-routerlab-schluessel"

In PowerShell:

powershell
RouterLab
$env:ROUTERLAB_API_KEY = "ihr-routerlab-schluessel"

So prüfen Sie die mit diesem Schlüssel verfügbaren Kennungen:

bash
RouterLab
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:

typescript
RouterLab
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:

bash
RouterLab
npx tsx classer-ticket.ts

Eine beispielhafte Antwort, die dem Vertrag entspricht:

json
RouterLab
{
  "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:

bash
RouterLab
export ROUTERLAB_MODEL="gemini-3.8-flash"
npx tsx classer-ticket.ts

Oder:

bash
RouterLab
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:

text
RouterLab
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:

typescript
RouterLab
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:

bash
RouterLab
python -m pip install pydantic

Speichern Sie in einem Linux- oder macOS-Terminal das vom TypeScript-Skript erzeugte JSON:

bash
RouterLab
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:

python
RouterLab
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:

bash
RouterLab
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.

SituationGeeignete Massnahme
Schema oder Parameter zurückgewiesenKompatibilität zwischen Modell, Route und Schema prüfen.
Verweigerung durch das ModellDie Verweigerung als separates Ergebnis behandeln.
Abgebrochene AntwortGenerierungsgrenzen und Umfang der Aufgabe prüfen.
Ungültiges JSONErgebnis verwerfen und die Ursache der fehlenden Vertragseinhaltung untersuchen.
Schema-VerletzungValidierungsfehler protokollieren und Ergebnis verwerfen.
Schema-konforme, aber falsche DatenAnweisungen, 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.
Endpoint RouterLab

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.

https://api.routerlab.ch/v1

Besuchermessung

Dürfen Google Analytics und RouterLab öffentliche Besuche messen und die Kampagnenquelle mit Ihrer Registrierung verknüpfen? Dies ist freiwillig und jederzeit änderbar. Bei Ablehnung werden nur anonyme Besuchszahlen gespeichert.