L'article précédentPourquoi les agents d'IA ne peuvent pas vivre sans JSONtracé Tool Calling et MCP saut par saut. Celui-ci regarde leréponse finalele modèle donne à un utilisateur ou à un programme en aval : comment faire en sorte que Gemini émette JSON que vous pouvez analyser, valider et stocker - pas de prose qui ressemble simplement à JSON.
Il s'agit des sorties structurées (génération contrôlée) dans la documentation Gemini. Il partage des idées de schéma avec Function Calling mais une cible différente : la première contraint lecharge utile finale; ce dernier contraintoutil arguments. Gardez-les séparés afin qu'un Agent ne traite pas « extraire une facture » et « appeler le paiement API » comme le même type d'appel.
Trois approches, chacune plus stricte
Les équipes essaient généralement trois façons de « faire en sorte que Gemini produise JSON ». La fiabilité diffère d'un ordre de grandeur :
| Approche | Ce que vous contrôlez | Quand c'est suffisant |
|---|---|---|
| Invite uniquement : « veuillez afficher JSON » | Contrainte douce ; Les clôtures de démarque et les commentaires finaux apparaissent toujours | Exploration, scripts ponctuels |
responseMimeType: application/json | La sortie doit être un texte JSON valide | La forme varie ; vous n'avez besoin que de parse() pour réussir |
MIME + responseSchema / responseJsonSchema | Les champs, types, énumérations et clés requises sont limités | Extraction de production, formulaires, charges utiles d'agent à agent |
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.
Décodage contraint : pourquoi Schema bat une invite
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 : un exemple complet de google-genai
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 Schéma
Ne devinez pas quelle clé de configuration utiliser :
- response_schema: un modèle Pydantic, un objet Python Enum ou un objet SDK Schema. Le SDK le mappe au sous-ensemble OpenAPI on-wire.
- response_json_schema: a JSON Schema object (dict). Use it for
Invoice.model_json_schema(), ZodtoJSONSchema(), and richer keywords such asadditionalProperties,minimum/maximum, andprefixItems.
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.
À quoi ressemble la requête REST
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.
Comment diviser le travail avec Function Calling
Les deux utilisent Schema pour gouverner JSON, mais ils reposent sur des sauts différents :
| Sortie structurée | Appel de fonction / Appel d'outil | |
|---|---|---|
| Qu'est-ce qui est contraint | Réponse finale JSON | Argument-outil JSON |
| Qui gère les effets secondaires | Personne; ce ne sont que des données | Hôte / MCP Serveur |
| Configuration typique | responseMimeType + Schéma | outils[].parameters / inputSchema |
| En cas d'échec | Réessayez ou revenez à un humain | Écrivez l'erreur sous forme de message d'outil et demandez à nouveau |
Extraction de factures, étiquettes de modération, transformation de notes en liste de tâches : sortie structurée. Recherche d'inventaire, création d'un ticket, lecture d'un fichier repo : outils — voirl'article sur le flux de donnéesetJSON Schéma et évolution MCP. N'utilisez pas la sortie structurée pour prétendre qu'un paiement API a déjà été exécuté — le modèle ne l'a pas appelé.
Validation de l'exécution et pièges courants
Le décodage contraint n’est pas une solution commerciale correcte. Gardez deux portes :
- Syntax and Schema: after
json.loads, validate again with the same JSON Schema (required, enum, minimum). - Business invariants: for example
sum(item.qty * item.unit_price_cents) == total_cents. Schema cannot express that; you write it.
Pièges courants :
- Mots clés non pris en charge: le dumping d'un projet complet de schéma 2020-12 peut ignorer silencieusement certains mots-clés. Commencez par type / propriétés / requis / enum / éléments, puis ajoutez additionalProperties et min/max.
- Array at the root:
{ "items": [ ... ] }as an object root is often more reliable. - Mélanger les commentaires Markdown avec JSON: une fois JSON MIME activé, ne demandez pas également « expliquez d'abord, puis JSON ».
- Troncature: augmentez maxOutputTokens, ou divisez « la liste d'abord, puis remplissez chaque ligne ».
- Clés dans le frontend: les démos à sortie structurée dans les clés API du navigateur fuient. Le schéma peut être public ; la clé reste sur le serveur.
Pendant le développement, conservez le schéma et deux ou trois échantillons positifs/négatifs dans git. Inspectez la structure et les différences localement dans JSON Toolbox — la même habitude de test de contrat que REST, avec Gemini comme consommateur.
FAQ
Quelle est la différence entre le type JSON MIME seul et l'envoi d'un schéma ?
Avec seulement responseMimeType application/json, le modèle tente d'émettre un JSON valide, mais les noms de champs, les types et les clés requises ne sont pas contraints. L'ajout de responseSchema ou responseJsonSchema contraint les jetons pendant le décodage, de sorte que la forme est suffisamment stable pour persister ou être transmise à l'agent suivant.
Comment choisir response_schema ou response_json_schema ?
Utilisez response_schema avec un modèle Pydantic ou un schéma SDK ; le SDK peut exposer Response.parsed. Utilisez response_json_schema pour un objet JSON Schema complet (additionalProperties, min/max, prefixItems) ou lors de l'envoi de Pydantic/Zod model_json_schema() tel quel. Les deux nécessitent response_mime_type=application/json.
La sortie structurée peut-elle remplacer Function Calling ?
Non. La sortie structurée contraint le JSON final que l'utilisateur ou le code en aval voit. Function Calling / Tool Calling contraint l'argument outil JSON et nécessite toujours que l'hôte exécute l'outil. Utiliser une sortie structurée pour l'extraction, la classification et le remplissage de formulaires ; utilisez des outils pour la météo, les fichiers et MCP. Les pipelines Agent utilisent souvent les deux.
Le modèle garantit-il une conformité à 100 % au schéma ?
Le décodage contraint réduit les erreurs de syntaxe et la dérive de type, mais des hallucinations sémantiques, des troncatures et des mots-clés ignorés et non pris en charge se produisent toujours. En production, exécutez le même schéma via un validateur et réessayez ou dégradez en cas d'échec.
Les objets imbriqués, les tableaux et les énumérations sont-ils pris en charge ?
Oui. Les objets, les tableaux et les énumérations de chaînes constituent la combinaison habituelle. Pour la classification, vous pouvez définir MIME sur text/x.enum afin que le modèle émette uniquement la valeur enum, pas un objet JSON. Les références d'imbrication très profondes ou cycliques peuvent être rejetées - aplatissez le schéma.
Comment puis-je valider le schéma et les exemples de sortie localement ?
Enregistrez responseJsonSchema et quelques exemples de sortie de modèle sous forme de fichiers JSON. Vérifiez la syntaxe et la structure localement dans JSON Toolbox dans le navigateur — rien n'est téléchargé. Utilisez à nouveau le même schéma au moment de l'exécution après l'expédition.
Résumé
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.