Strukturiertes JSON mit der Gemini API erzeugen: Entwicklerleitfaden

Von Prompt-JSON und responseMimeType bis responseSchema / JSON Schema — Structured Outputs in der Gemini API, Python- und REST-Beispiele, Abgrenzung zu Function Calling und Validierung vor dem Go-live.

Der vorherige ArtikelWarum KI-Agenten ohne JSON nicht leben könnenverfolgte Tool Calling und MCP Hop für Hop. Dieser schaut sich die anletzte AntwortDas Modell gibt einem Benutzer oder einem Downstream-Programm Folgendes: Wie man Gemini dazu bringt, JSON auszugeben, kann man analysieren, validieren und speichern – keine Prosa, die nur wie JSON aussieht.

Das sind strukturierte Ausgaben (kontrollierte Generierung) in den Gemini-Dokumenten. Es teilt Schema-Ideen mit Funktionsaufruf, hat aber ein anderes Ziel: Ersteres schränkt das einendgültige Nutzlast; Letzteres schränkt einWerkzeug Argumente. Halten Sie sie auseinander, damit ein Agent „Rechnung extrahieren“ und „Zahlungs-API aufrufen“ nicht als dieselbe Art von Anruf behandelt.

Drei Ansätze, jeweils strenger

Teams versuchen normalerweise drei Möglichkeiten, „die Gemini-Ausgabe JSON zu machen“. Die Zuverlässigkeit unterscheidet sich um eine Größenordnung:

AnsatzWas Sie kontrollierenWenn es reicht
Nur Eingabeaufforderung: „Bitte geben Sie JSON aus“Weiche Einschränkung; Abschlagszäune und nachgestellte Kommentare erscheinen weiterhinErkundung, einmalige Skripte
responseMimeType: application/jsonDie Ausgabe muss gültiger JSON-Text seinForm variiert; Sie brauchen nur parse(), um erfolgreich zu sein
MIME + responseSchema / responseJsonSchemaFelder, Typen, Aufzählungen und erforderliche Schlüssel sind eingeschränktProduktionsextraktion, Formulare, Agent-zu-Agent-Nutzlasten

Everyone has seen the first failure mode: a ```json fence, an extra paragraph, single quotes, a trailing comma. The second layer parses, but price may be a string and items may be missing. The third layer is this tutorial: hand JSON Schema to the API so the decoder avoids illegal paths at each token.

Eingeschränkte Dekodierung: Warum Schema eine Eingabeaufforderung übertrifft

A prompt only raises the odds that the model wants to comply. Structured Output compiles the Schema into generation: if the next token would break JSON syntax or leave the Schema (for example starting an undeclared field), its probability is suppressed. So response.text is usually a parseable object — no regex to strip fences.

Since 2025 the Gemini API complements the OpenAPI 3.0-style responseSchema with standard JSON Schema (often responseJsonSchema on the wire). Pydantic model_json_schema() and Zod exports can be sent almost as-is. Gemini 2.5 and later also tend to preserve property order from the Schema, which helps CSV columns and tables downstream.

Classification has a side path: responseMimeType: text/x.enum emits only the enum string (for example Keyboard), with no braces. Use application/json when you need an object; use the enum MIME when you need a single label.

Python: ein vollständiges google-genai-Beispiel

Prefer the current SDK google-genai (from google import genai). Do not mix it with the legacy google-generativeai package. With GEMINI_API_KEY set:

from google import genai
from pydantic import BaseModel, Field


class LineItem(BaseModel):
    name: str = Field(description="Product name")
    qty: int = Field(description="Quantity, positive integer")
    unit_price_cents: int = Field(description="Unit price in cents")


class Invoice(BaseModel):
    vendor: str
    currency: str = Field(description="ISO 4217, e.g. CNY")
    items: list[LineItem]
    total_cents: int


client = genai.Client()
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Extract an invoice from: Acme sold 2 keyboards at 199 CNY each.",
    config={
        "response_mime_type": "application/json",
        "response_schema": Invoice,
    },
)

print(response.text)      # JSON string
invoice = response.parsed  # Invoice instance (Pydantic path)
print(invoice.total_cents)

response.parsed is meaningful when response_schema is a Pydantic or SDK type. If you pass a raw JSON Schema dict (next section), json.loads(response.text) and validate yourself.

For many records use list[Invoice] or wrap invoices: list[Invoice] in an object. An array at the root is less stable on some models than always returning an object; production code usually does the latter.

response_schema vs. JSON Schema

Raten Sie nicht, welchen Konfigurationsschlüssel Sie verwenden sollen:

  • response_schema: ein Pydantic-Modell, ein Python-Enum- oder ein SDK-Schema-Objekt. Das SDK ordnet es der On-Wire-OpenAPI-Teilmenge zu.
  • response_json_schema: a JSON Schema object (dict). Use it for Invoice.model_json_schema(), Zod toJSONSchema(), and richer keywords such as additionalProperties, minimum / maximum, and prefixItems.
schema = {
  "type": "object",
  "properties": {
    "vendor": { "type": "string" },
    "currency": { "type": "string", "enum": ["CNY", "USD", "EUR"] },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "qty": { "type": "integer", "minimum": 1 },
          "unit_price_cents": { "type": "integer", "minimum": 0 }
        },
        "required": ["name", "qty", "unit_price_cents"],
        "additionalProperties": False
      }
    },
    "total_cents": { "type": "integer" }
  },
  "required": ["vendor", "currency", "items", "total_cents"],
  "additionalProperties": False
}

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Extract the invoice: …",
    config={
        "response_mime_type": "application/json",
        "response_json_schema": schema,
    },
)

Older REST docs use uppercase types in responseSchema (OBJECT, STRING, ARRAY, INTEGER). The JSON Schema path uses lowercase object / string. Do not mix the two keyword sets. Put field meaning in description: it enters the model context and decides whether qty is pieces or cases. Types alone cannot.

Wie die REST-Anfrage aussieht

On the Gemini Developer API, generateContent puts structured output under generationConfig. The key goes in x-goog-api-key or a query param — never in a frontend repo.

POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent

{
  "contents": [
    {
      "role": "user",
      "parts": [{ "text": "Extract an invoice from the text: …" }]
    }
  ],
  "generationConfig": {
    "responseMimeType": "application/json",
    "responseJsonSchema": {
      "type": "object",
      "properties": {
        "vendor": { "type": "string" },
        "total_cents": { "type": "integer" }
      },
      "required": ["vendor", "total_cents"]
    }
  }
}

On success the candidate text is candidates[0].content.parts[0].text — a JSON string. Vertex AI uses the same field names; only the endpoint and GCP auth change. Images and PDFs can be inputs: the Schema constrains output, not multimodal input.

So teilen Sie die Arbeit mit Function Calling auf

Beide verwenden Schema, um JSON zu steuern, aber sie befinden sich auf unterschiedlichen Hops:

Strukturierte AusgabeFunktionsaufruf / Tool-Aufruf
Was ist eingeschränktEndgültige Antwort JSONTool-Argument JSON
Wer hat Nebenwirkungen?Niemand; es sind nur DatenHost / MCP Server
Typische KonfigurationresponseMimeType + Schematools[].parameters / inputSchema
Beim ScheiternVersuchen Sie es noch einmal oder greifen Sie auf einen Menschen zurückSchreiben Sie den Fehler als Tool-Nachricht und fragen Sie erneut

Rechnungen extrahieren, Moderationsetiketten erstellen, Notizen in eine Aufgabenliste umwandeln: Strukturierte Ausgabe. Inventarsuche, Erstellen eines Tickets, Lesen einer Repo-Datei: Tools – sieheDer Datenfluss-ArtikelUndJSON Schema und MCP Evolution. Verwenden Sie die strukturierte Ausgabe nicht, um so zu tun, als ob eine Zahlungs-API bereits ausgeführt wurde – das Modell hat sie nicht aufgerufen.

Laufzeitvalidierung und häufige Fallstricke

Eine eingeschränkte Dekodierung ist keine geschäftliche Korrektheit. Behalten Sie zwei Tore:

  1. Syntax and Schema: after json.loads, validate again with the same JSON Schema (required, enum, minimum).
  2. Business invariants: for example sum(item.qty * item.unit_price_cents) == total_cents. Schema cannot express that; you write it.

Häufige Fallstricke:

  • Nicht unterstützte Schlüsselwörter: Beim Dumping eines vollständigen Entwurfs 2020-12-Schemas werden möglicherweise einige Schlüsselwörter stillschweigend ignoriert. Beginnen Sie mit Typ / Eigenschaften / Erforderlich / Aufzählung / Elemente und fügen Sie dann additionalProperties und Min/Max hinzu.
  • Array at the root: { "items": [ ... ] } as an object root is often more reliable.
  • Mischen von Markdown-Kommentaren mit JSON: Sobald JSON MIME aktiviert ist, fragen Sie nicht mehr nach „erst erklären, dann JSON“.
  • Kürzung: maxOutputTokens erhöhen oder „zuerst auflisten, dann jede Zeile füllen“ aufteilen.
  • Schlüssel im Frontend: Demos mit strukturierter Ausgabe im Browser lecken API-Schlüssel. Das Schema kann öffentlich sein; Der Schlüssel bleibt auf dem Server.

Behalten Sie während der Entwicklung das Schema und zwei oder drei positive/negative Beispiele in Git. Überprüfen Sie Struktur und Diff lokal in der JSON Toolbox – die gleiche Vertragstestgewohnheit wie REST, mit Gemini als Verbraucher.

FAQ

Was ist der Unterschied zwischen dem alleinigen MIME-Typ JSON und dem Senden eines Schemas?

Mit nur responseMimeType application/json versucht das Modell, gültiges JSON auszugeben, aber Feldnamen, Typen und erforderliche Schlüssel sind nicht eingeschränkt. Durch das Hinzufügen von responseSchema oder responseJsonSchema werden Token während der Dekodierung eingeschränkt, sodass die Form stabil genug ist, um bestehen zu bleiben oder an den nächsten Agenten weitergegeben zu werden.

Wie wähle ich response_schema vs. response_json_schema?

Verwenden Sie response_schema mit einem Pydantic-Modell oder SDK-Schema; Das SDK kann „response.parsed“ verfügbar machen. Verwenden Sie response_json_schema für ein vollständiges JSON Schema-Objekt (additionalProperties, min/max, prefixItems) oder beim Senden von Pydantic/Zod model_json_schema() wie es ist. Beide erfordern response_mime_type=application/json.

Kann eine strukturierte Ausgabe den Funktionsaufruf ersetzen?

Nein. Die strukturierte Ausgabe schränkt den endgültigen JSON ein, den der Benutzer oder Downstream-Code sieht. Function Calling / Tool Calling schränkt das Tool-Argument JSON ein und erfordert weiterhin, dass der Host das Tool ausführt. Verwenden Sie strukturierte Ausgaben zum Extrahieren, Klassifizieren und Ausfüllen von Formularen. Verwenden Sie Tools für Wetter, Dateien und MCP. Agent-Pipelines verwenden häufig beides.

Garantiert das Modell 100 % Schemakonformität?

Durch die eingeschränkte Dekodierung werden Syntaxfehler und Typdrift deutlich reduziert, es kommt jedoch immer noch zu semantischen Halluzinationen, Kürzungen und ignorierten, nicht unterstützten Schlüsselwörtern. Führen Sie in der Produktion dasselbe Schema durch einen Validator aus und versuchen Sie es erneut oder verschlechtern Sie es bei einem Fehler.

Werden verschachtelte Objekte, Arrays und Aufzählungen unterstützt?

Ja. Objekte, Arrays und String-Enums sind die übliche Kombination. Zur Klassifizierung können Sie MIME auf text/x.enum setzen, sodass das Modell nur den Enum-Wert ausgibt, kein JSON-Objekt. Sehr tiefe Verschachtelungen oder zyklische Refs können abgelehnt werden – reduzieren Sie das Schema.

Wie validiere ich die Schema- und Beispielausgabe lokal?

Speichern Sie responseJsonSchema und einige Modellausgabebeispiele als JSON-Dateien. Überprüfen Sie Syntax und Struktur lokal in der JSON Toolbox im Browser – es wird nichts hochgeladen. Verwenden Sie dasselbe Schema nach der Auslieferung erneut zur Laufzeit.

Zusammenfassung

To get structured JSON from Gemini, the order is: Schema first, JSON MIME second, prompt last. The prompt owns meaning (what to extract); the Schema owns shape (what fields look like). Pydantic / Zod are author-friendly fronts; on the wire you send response_schema or response_json_schema.

Run one real invoice or a support transcript: write the Schema → call generateContent once → paste response.text into a validator. Only then wire a database or the next agent. Tool arguments still go through Function Calling / MCP — do not collapse them into one API.