Tutorial

Wie gibt die Gemini API JSON aus? Structured Output und JSON Schema im Überblick

Bitten Sie das Modell nicht mehr, „bitte nur JSON“ zu liefern. Mit Structured Output die Form festnageln, mit JSON Schema die Felder binden – dann kann der Downstream-Code zuverlässig parsen.

Sie bitten Gemini um JSON und bekommen eine Vorrede, ein fehlendes Komma oder freundlichere Feldnamen. Dem Prompt „nur JSON ausgeben“ zu sagen, senkt die Chancen; es ist kein Vertrag. Structured Output legt den Vertrag ins Decoding: MIME-Typ deklarieren, Schema anhängen, und das Modell gibt Tokens in dieser Form aus. Nach diesem Leitfaden können Sie JSON-Modus vs. Schema-Modus wählen, eine funktionierende Anfrage schreiben und trotzdem lokal validieren.

Warum strukturierte Ausgabe

Sobald der Downstream JSON.parse aufruft, ist der Fehlschlag kein Kopierproblem — die ganze Pipeline steht. Klassifizierer brauchen feste Enums, Extraktoren stabile Schlüssel, Tool-Aufrufe Parameterobjekte. Freiform-Antworten sind teuer: ein kaputtes JSON bedeutet Retries, Logs oder noch einen Klick vom Nutzer.

Ein leiserer Fehler ist „es parst, aber die Form stimmt nicht“. Sie erwarteten items als Array und bekamen ein Objekt; Sie erwarteten score als Zahl und bekamen "0.9". Der Code liest undefined, und der Bug explodiert viel später. Structured Output korrigiert die Form, nicht die Wahrheit: Sie erhalten legales, schemaförmiges JSON, keine garantiert richtige Kategorie. Produktion braucht weiter Geschäftsprüfungen — das Parsen wird nur ruhiger.

Die offiziellen Fähigkeitshinweise stehen in den Gemini-Structured-output-Docs. Google hat außerdem breitere JSON-Schema-Keywords und eine feste Property-Reihenfolge angekündigt; lesen Sie das Structured-Outputs-Update. Bevor Sie ein Schema schreiben, überfliegen Sie Understanding JSON Schema, damit Sie „Keywords, die die Spec erlaubt“ nicht mit „Keywords, die dieses Modell wirklich erzwingt“ verwechseln.

JSON-Modus vs. Schema-Modus

Denken Sie an zwei Schalter. Der erste verspricht nur: „dieser String parst als JSON“. Der zweite verspricht: „dieses JSON passt zum Schema, das Sie deklariert haben“. Liest Ihr Code benannte Felder, nehmen Sie den zweiten. Den ersten nur für explorative Extraktion, bei der das Modell auch Schlüssel erfindet.

Stufe Was Sie konfigurieren Was Sie tatsächlich bekommen
JSON-Modus Nur responseMimeType: application/json Meist gültiges JSON; Namen und Verschachtelung bleiben Sache des Modells
Schema-Modus MIME-Typ + responseSchemaoderresponseJsonSchema Form, Typen, Pflichtfelder und Enums folgen dem Schema

Mit JSON-Modus allein behandeln die Docs das noch als starken Hinweis mit einem kleinen Risiko auf missgebildete Ausgabe. Um nah an „immer als Objekt parsen“ zu kommen, schicken Sie auch ein Schema. Das Schema zählt zu den Input-Tokens: kleben Sie dieselbe Beschreibung nicht noch einmal in den Prompt. Duplikate schaden Qualität und Quota.

responseSchema oder responseJsonSchema

responseSchema nutzt eine Schema-Teilmenge im OpenAPI-3.0-Stil. REST-Typnamen sind oft großgeschrieben, etwa OBJECT, STRING. Es passt zu flachen Objekten, Enum-Klassifikation und zum Festnageln der Schlüsselreihenfolge mit propertyOrdering. Es versteht kein $ref / $defs, daher müssen rekursive Bäume und geteilte Defs inlined werden und explodieren bald in der Größe.

responseJsonSchema zielt auf Gemini 2.5 und neuer und spricht näher am Standard-JSON-Schema, inklusive anyOf, $ref, minimum / maximum, additionalProperties, type: null, prefixItems und mehr. Das Schema aus Pydantic oder Zod zu erzeugen, senkt die Reibung. Neuere Modelle behalten die Schlüsselreihenfolge wie deklariert — gut für Log-Diffs und Golden Tests.

Drei Faustregeln. Flach klassifizieren/extrahieren: jedes der beiden Felder reicht. Rekursion, geteilte Defs oder Unions: lieber responseJsonSchema. Wenn Sie die Feldreihenfolge festnageln müssen, prüfen Sie, ob der Endpoint noch propertyOrdering ehrt; unterstellen Sie nicht, dass jede API-Oberfläche gleich tickt.

So schreiben Sie das Schema

Beschreiben Sie das Objekt, das Sie wirklich lesen, nicht ein komplettes Weltmodell. Jedes extra optionale Feld ist eine Chance, Müll zu füllen. Pflichtnamen in required, Enums in enum, numerische Grenzen in minimum / maximum. Ergänzen Sie description an den Properties; das ist oft stabiler, als sie noch einmal im Prompt zu erklären, weil die Constraint mit dem Decoding mitreist.

Das Schema unten modelliert Ticket-Klassifikation: category ist einer von drei Werten, priority eine Ganzzahl, summary ein String. Das soll der Schema-Modus besitzen — die Form, nicht ob das Ticket wirklich dringend ist.

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "bug", "feature"],
      "description": "Ticket category"
    },
    "priority": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5
    },
    "summary": {
      "type": "string"
    }
  },
  "required": ["category", "priority", "summary"],
  "additionalProperties": false
}

Arrays nutzen items für Elemente. Für tupelartige feste Listen schauen Sie auf dem JSON-Schema-Pfad nach prefixItems. Behandeln Sie „nicht in required“ nicht als nullable — erlauben Sie null explizit, sonst lässt das Modell den Schlüssel weg, während Ihr Code noch annimmt, dass obj.field immer existiert.

Schachteln Sie verschachtelte Objekte inline. Greifen Sie zu $defs + $ref erst, wenn dieselbe Struktur zum dritten Mal vorkommt oder ein Baumknoten sich selbst referenziert. Frühe Abstraktion macht Ablehnungen schwer lesbar: Sie debuggen einen expandierten Blob, wenn der Server das Schema ablehnt.

Python und JavaScript in der Praxis

Die Snippets nutzen gängige offizielle SDK-Formen. Ersetzen Sie die Modell-ID durch die 2.5-/neuere SKU, die Ihr Projekt wirklich hat — behandeln Sie den Beispielnamen nicht als eingefrorenen Produktions-Pin.

Python: MIME-Typ + JSON Schema

from google import genai

client = genai.Client()
schema = {
    "type": "object",
    "properties": {
        "category": {"type": "string", "enum": ["billing", "bug", "feature"]},
        "priority": {"type": "integer"},
        "summary": {"type": "string"},
    },
    "required": ["category", "priority", "summary"],
}

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Classify this ticket: invoice PDF cannot be downloaded.",
    config={
        "response_mime_type": "application/json",
        "response_json_schema": schema,
    },
)
print(response.text)

Wenn das Team schon mit Pydantic modelliert, übergeben Sie Model.model_json_schema() an response_json_schema, dann model_validate_json(response.text) für einen zweiten lokalen Check. Schicht eins ist die API-Form; Schicht zwei ist Ihr Typsystem, das Werte ablehnt, die legal aussehen, aber Unsinn sind, etwa priority 99.

JavaScript: generationConfig

const response = await ai.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "Classify this ticket: invoice PDF cannot be downloaded.",
  config: {
    responseMimeType: "application/json",
    responseJsonSchema: {
      type: "object",
      properties: {
        category: { type: "string", enum: ["billing", "bug", "feature"] },
        priority: { type: "integer" },
        summary: { type: "string" },
      },
      required: ["category", "priority", "summary"],
    },
  },
});
const data = JSON.parse(response.text);

REST-Aufrufe setzen dieselben Felder auf generationConfig. OpenAPI-artiges responseSchema nutzt auf manchen Endpoints noch Großbuchstaben-Typen — mischen Sie das nicht mit dem kleingeschriebenen object von JSON Schema. Parsen Sie sofort mit JSON.parse; kratzen Sie keinen fenced Codeblock per Regex. Sie haben JSON-MIME deklariert, also behandeln Sie den ganzen Body als JSON.

Typische Fallstricke

  • Die Struktur zweimal zu schreiben — einmal im Prompt, einmal im Schema — lässt das Modell zwischen den beiden Beschreibungen wackeln.
  • Übergroße Schemas: tiefe Verschachtelung, tiefes $ref oder breites anyOf können abgelehnt oder nur schwach erzwungen werden. Schicken Sie zuerst ein winziges Objekt, dann wachsen Sie es.
  • Lagern Sie Wahrheit nicht an das Schema aus. Enums begrenzen die Menge; sie stoppen keine falsche Wahl. Stichproben oder Regeln auf kritischen Pfaden.
  • Lokale Typen driften vom Request-Schema weg. Sie ändern Pydantic/Zod und vergessen das Payload-Schema; Produktion bekommt still extra Keys oder verliert alte.
  • Testen Sie nicht nur den Happy Path. Ergänzen Sie leere Arrays, nullable Felder, lange Strings und illegale Enums (die blockiert gehören).

Noch ein Engineering-Thema: Logs sollten nicht nur response.text speichern. Notieren Sie Modell-ID, Schema-Hash und Prompt-Version. Structured-Output-Brüche sind meist „Defaults haben sich geändert“ oder „eine Mini-Schema-Änderung wurde abgelehnt“. Ohne diese drei schwören Sie, dass es gestern ging.

Ausliefern: validieren, vergleichen, retry

Behandeln Sie den API-Body als untrusted Bytes. Parsen, gegen dasselbe Schema validieren, dann auf interne Typen mappen. Bei Fehler den Rohtext loggen (Secrets schwärzen) und Retry vs. Degradieren wählen. Retryen Sie nicht mit einem völlig anderen Schema, sonst können Sie Modellrauschen nicht von einem wandernden Vertrag trennen.

Beim Debuggen Samples in die On-Site-Tools kleben. JSON-Formatierung, um Verschachtelung zu sehen, JSON validieren, um Syntax zu fangen, dann den Vertrag in JSON Schema legen, ob die Instanz durchgeht. Wenn Felder auftauchen oder verschwinden, JSON Diff zwei Antworten, statt Logs mit dem Auge zu scannen.

Wenn Sie eine Extraktions-Pipeline entwerfen, schreiben Sie eine „Idealausgabe“ von Hand, inferieren Typen mit dem Schema-Tool und kleben dieses Schema zurück in die Gemini-Anfrage. Der Vertrag lebt dann an einem Ort: ein Dokument, das Sie testen können, kein mündlicher Deal im Chatverlauf.

Häufige Fragen FAQ

Reicht application/json allein?

Fein zum Explorieren. Sobald Code feste Felder liest, schicken Sie auch ein Schema. Sonst bekommen Sie JSON-förmige Prosa, keine API.

Kann Structured Output Function Calling ersetzen?

Nein. Function Calling lässt das Modell ein Tool wählen und Argumente füllen. Structured Output zwingt die Form dieser Antwort. Code ausführen oder eine externe API treffen? Tools. Ein typisiertes Blob? Structured Output und Sie sparen eine Roundtrip.

Warum wurde mein Schema abgelehnt?

Meist nicht unterstützte Keywords auf diesem Endpoint, zu tiefe Rekursion oder das Mischen der Dialekte responseSchema und responseJsonSchema. Schrumpfen Sie auf ein Objekt mit drei Feldern, beweisen Sie, dass es läuft, dann ergänzen.

Ist die Ausgabe immer korrekt?

Nein. Die Form kann gültig sein, die Fakten falsch. Geld, E-Mails und Ticket-Kategorien brauchen weiter Regeln oder menschliches Sampling.

Fazit und nächste Schritte

Zuverlässiges JSON kommt nicht von einem längeren „bitte nur JSON ausgeben“. Es kommt von MIME-Typ plus Schema. Für flache Jobs ist responseSchemaoderresponseJsonSchema in Ordnung; für $ref, Unions oder Schemas aus Pydantic/Zod nehmen Sie Letzteres. Validieren Sie trotzdem lokal und machen Sie Fehler mit Format, Schema und Diff zu Regressionstests.

Nächster Schritt: nehmen Sie den fragilsten Endpoint, den Sie haben, und machen Sie daraus „ein Schema, eine Anfrage, eine lokale Validierung“. Beruhigen Sie diesen Pfad zuerst, dann kopieren Sie das Muster.