Verlässliches JSON aus einem LLM – mit Structured Outputs statt Hoffnung
Antworte nur mit JSON" reicht nicht – irgendwann kommt doch ein "Klar, hier ist dein JSON:" davor und dein JSON.parse() stürzt ab. Mit einem JSON-Schema erzwingst du garantiert gültige Struktur.
Wer ein LLM programmatisch nutzt, will die Antwort meist als Datenstruktur, nicht als Fließtext – z. B. { "kategorie": "...", "dringlichkeit": 3 }. Der naive Weg ist, im Prompt „Antworte nur mit JSON" zu schreiben und das Ergebnis durch JSON.parse() zu jagen. Das geht so lange gut, bis das Modell doch ein „Klar, hier ist dein JSON:" davorsetzt oder ein Feld weglässt – und deine App abstürzt. Die saubere Lösung heißt Structured Outputs: Du gibst ein JSON-Schema vor, und die API garantiert eine dazu passende Antwort.
Ein Schema erzwingen (OpenAI)
// Läuft auf dem Server – der API-Key gehört nicht ins Frontend
async function classify(text) {
const res = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [{ role: 'user', content: `Klassifiziere: ${text}` }],
response_format: {
type: 'json_schema',
json_schema: {
name: 'ticket',
strict: true, // erzwingt exakt dieses Schema
schema: {
type: 'object',
properties: {
kategorie: { type: 'string', enum: ['bug', 'frage', 'lob'] },
dringlichkeit: { type: 'integer' },
},
required: ['kategorie', 'dringlichkeit'],
additionalProperties: false, // keine Extra-Felder
},
},
},
}),
})
const data = await res.json()
// Mit strict:true ist das immer gültiges, schema-konformes JSON
return JSON.parse(data.choices[0].message.content)
}
const t = await classify('Der Login hängt seit dem Update')
console.log(t.kategorie, t.dringlichkeit) // z. B. "bug" 3Warum das den Unterschied macht
strict: true+additionalProperties: falsegarantieren: keine Vorrede, keine fehlenden Pflichtfelder, keine erfundenen Zusatzfelder.JSON.parse()kann nicht mehr an unerwartetem Text scheitern.enumgrenzt Werte ein – so bekommst du nicht mal „Bug", „BUG" und „fehler" durcheinander, sondern genau deine drei erlaubten Kategorien.- Trotzdem defensiv bleiben: Bei einem Sicherheits-Refusal oder abgeschnittener Antwort (Token-Limit erreicht) kann das JSON ausbleiben. Ein
try/catchumJSON.parsegehört weiterhin dazu.
Fast jeder große Anbieter kennt dieses Muster: OpenAI über response_format, Claude (Anthropic) über output_config.format bzw. Tool-Use, Gemini über responseSchema. Das Prinzip ist überall gleich – Struktur erzwingen statt erhoffen.
Du willst KI-Ausgaben zuverlässig in deine App-Logik einbinden? Sprich mich an.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
Web Worker – rechnen, ohne die Oberfläche einzufrieren
JavaScript hat im Browser genau einen Faden für alles: Klicks, Animationen, Layout – und deine Rechenschleife. Dauert die zwei Sekunden, ist die Seite zwei Sekunden lang tot. Ein Worker nimmt die Arbeit auf einen eigenen Faden.
Formulare ohne Neuladen abschicken – FormData und fetch
Ein Formular per JavaScript abzuschicken heißt nicht, jedes Feld einzeln auszulesen. FormData nimmt das ganze Formular, fetch schickt es weg – inklusive Dateien, und mit einer Fehlermeldung, die der Besucher versteht.
CustomEvent – Bausteine, die sich gegenseitig Bescheid sagen, ohne sich zu kennen
Sobald zwei Teile einer Seite aufeinander reagieren sollen, landet man schnell bei globalen Variablen oder Funktionsaufrufen quer durchs Projekt. Eigene Events entkoppeln das – mit denselben Mitteln, die der Browser selbst benutzt.