Structured Output Is an API: Evolving Gemini Schemas Without Breaking Your Agent A developer outlines a versioning approach for Gemini structured-output schemas, arguing that once a JSON schema is consumed by tools, UIs, agents, or databases it becomes an API that must be evolved additively rather than silently changed. The writeup recommends embedding a schemaVersion discriminator in persisted values, using Zod with the Gemini JavaScript SDK's JSON Schema support, and separating syntax, shape, semantic, and policy validation so that policy violations are blocked rather than retried with reprompts. Structured output solves an important model problem: instead of asking Gemini for “JSON-like” prose, you provide a schema and receive syntactically structured data. Then the software-engineering problem begins. Once a tool, UI, downstream agent, database, or audit process depends on that JSON, the model output is an API. Renaming a field can break consumers. Adding an enum value can send an older UI into an impossible state. Valid JSON can still describe an invalid business decision. Suppose a travel assistant initially returns: type TravelDecisionV1 = { schemaVersion: "1"; action: "book" | "ask" | "wait"; reason: string; }; Later, the product needs suppression and evidence references. Quietly changing the original shape is risky. Introduce a new discriminated version: type TravelDecisionV2 = { schemaVersion: "2"; decision: "act" | "ask" | "wait" | "suppress"; reasonCode: string; evidenceIds: string ; confidence?: { value: number; source: "classifier" | "model" | "rule"; }; }; type TravelDecision = TravelDecisionV1 | TravelDecisionV2; The version belongs in the persisted value, not only in the deployment configuration. Stored outputs can outlive the model call that created them. Gemini supports structured output through a provided JSON Schema. Its JavaScript SDK can use a JSON Schema together with Zod validation. The abbreviated example below keeps the model identifier in configuration: js import { GoogleGenAI } from "@google/genai"; import as z from "zod"; const decisionSchema = z.object { schemaVersion: z.literal "2" , decision: z.enum "act", "ask", "wait", "suppress" , reasonCode: z.string .min 1 , evidenceIds: z.array z.string , } ; const jsonSchema = z.toJSONSchema decisionSchema ; const client = new GoogleGenAI {} ; const response = await client.interactions.create { model: process.env.GEMINI MODEL , input: "Evaluate the synthetic travel-change fixture.", response format: { type: "text", mime type: "application/json", schema: jsonSchema, }, } ; const decision = decisionSchema.parse JSON.parse response.output text , ; The Gemini structured-output documentation https://ai.google.dev/gemini-api/docs/structured-output explicitly recommends application validation. Gemini implements a subset of JSON Schema, and very large or deeply nested schemas may be rejected. For a public contract, make the schema as closed and bounded as the supported Gemini subset allows. Limit array sizes, constrain string formats where meaningful, and reject unexpected properties at the application boundary. A permissive schema followed by a strict consumer only moves the failure downstream. “The response was valid” can mean several different things: | Check | Example | |---|---| | Syntax | The response parses as JSON | | Shape | Required properties and enum values match the schema | | Semantics | Referenced evidence IDs exist | | Policy | act is permitted for this user and current state | Structured generation helps primarily with the first two. Your application still owns the last two. function validateSemantics d: TravelDecisionV2, knownIds: Set