Schémas Zod en TypeScript
Mis à jour le 29 juillet 2026
Zod : l’équivalent TypeScript de Pydantic
Si vous travaillez en TypeScript ou en JavaScript, Zod est la bibliothèque de validation de schémas utilisée par le SDK Mistral pour les Custom Structured Outputs. Elle joue exactement le même rôle que Pydantic en Python : définir un schéma strict que le modèle doit respecter. Tout ce que vous avez appris sur les schémas se transpose donc, à la syntaxe près.
L’installation réunit le SDK et Zod en une commande :
npm install @mistralai/mistralai zod
Zod fonctionne nativement avec TypeScript et fournit une inférence de types automatique — c’est son principal avantage sur une simple interface écrite à la main.
Définir un schéma simple
Un schéma Zod se construit avec z.object(), chaque champ recevant son validateur. La ligne z.infer est celle qui fait la différence : elle déduit le type TypeScript du schéma, ce qui vous évite de maintenir deux déclarations parallèles qui finiraient par diverger.
import { z } from "zod";
const ContactSchema = z.object({
name: z.string(),
email: z.string(),
phone: z.string(),
company: z.string(),
});
// TypeScript infère automatiquement le type
type Contact = z.infer<typeof ContactSchema>;
// => { name: string; email: string; phone: string; company: string }
Les types dont vous aurez besoin
Zod couvre tous les besoins des sorties structurées. Le schéma de ticket ci-dessous rassemble les cas courants : champs de base, champ nullable pour un assigné non défini, énumération pour la priorité, et tableaux typés.
import { z } from "zod";
const TicketSchema = z.object({
// Types de base
title: z.string(),
description: z.string(),
ticketId: z.number(),
confidence: z.number(),
isUrgent: z.boolean(),
// Optionnel (peut être null)
assignee: z.string().nullable(),
// Enum (valeurs contraintes)
priority: z.enum(["low", "medium", "high", "critical"]),
// Listes
tags: z.array(z.string()),
relatedIds: z.array(z.number()),
});
type Ticket = z.infer<typeof TicketSchema>;
Une nuance mérite attention : là où Python distingue int et float, Zod ne connaît que z.number(). Si vous portez un schéma d’un langage à l’autre, gardez cette correspondance sous les yeux.
| Pydantic (Python) | Zod (TypeScript) |
|---|---|
str | z.string() |
int, float | z.number() |
bool | z.boolean() |
list[str] | z.array(z.string()) |
Optional[str] | z.string().nullable() |
Enum | z.enum([...]) |
Schémas imbriqués
Comme avec Pydantic, un schéma peut en contenir un autre : il suffit de référencer la constante. La composition se lit alors du plus interne au plus externe, l’adresse dans l’entreprise, l’entreprise dans la personne.
import { z } from "zod";
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
postalCode: z.string(),
country: z.string(),
});
const CompanySchema = z.object({
name: z.string(),
industry: z.string(),
address: AddressSchema,
});
const PersonSchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string(),
role: z.string(),
company: CompanySchema,
});
type Person = z.infer<typeof PersonSchema>;
Un appel complet avec le SDK Mistral
Voici la même extraction de livre que dans la leçon Python, transposée en TypeScript. Deux points de vigilance apparaissent : le paramètre s’appelle responseFormat en camelCase, et parsed doit être testé avant usage, car le SDK le type comme potentiellement nul.
import Mistral from "@mistralai/mistralai";
import { z } from "zod";
// 1. Définir le schéma
const BookSchema = z.object({
title: z.string().describe("Titre du livre"),
authors: z.array(z.string()).describe("Liste des auteurs"),
year: z.number().nullable().describe("Année de publication"),
genre: z.string().describe("Genre littéraire principal"),
summary: z.string().describe("Résumé en 2-3 phrases"),
});
type Book = z.infer<typeof BookSchema>;
// 2. Créer le client
const client = new Mistral({ apiKey: "votre-clé-api" });
// 3. Appeler chat.parse()
async function extractBook(text: string): Promise<Book> {
const response = await client.chat.parse({
model: "mistral-large-latest",
messages: [
{
role: "system",
content: "Tu es un bibliothécaire expert. Extrais les informations du livre.",
},
{ role: "user", content: text },
],
responseFormat: BookSchema,
temperature: 0,
});
// 4. Accéder à l'objet parsé
const parsed = response.choices[0].message.parsed;
if (!parsed) {
throw new Error("Parsed est null — réponse peut-être tronquée");
}
return parsed;
}
// 5. Utilisation
async function main() {
const book = await extractBook(
"J'ai adoré '1984' de George Orwell, publié en 1949. Un classique dystopique."
);
console.log(`Titre : ${book.title}`);
console.log(`Auteurs : ${book.authors.join(", ")}`);
console.log(`Année : ${book.year}`);
console.log(`Genre : ${book.genre}`);
// Le JSON brut est aussi disponible
const raw = response.choices[0].message.content;
console.log(`JSON brut : ${raw}`);
}
main();
Décrire les champs avec .describe()
.describe() remplit en Zod le rôle de Field(description=...) en Pydantic : préciser au modèle ce que le champ doit contenir, en particulier quand le nom seul est ambigu. Un score sans description peut arriver sur une échelle de 0 à 100 ; avec la mention explicite de l’intervalle, il respecte votre convention.
const SentimentSchema = z.object({
sentiment: z.enum(["positive", "negative", "neutral"]).describe(
"Le sentiment global du texte"
),
score: z.number().describe(
"Score de confiance entre 0.0 et 1.0"
),
keywords: z.array(z.string()).describe(
"Les 3-5 mots-clés principaux"
),
summary: z.string().describe(
"Résumé en une phrase maximum"
),
});
Gestion des erreurs
Le pattern de retry suit la même logique qu’en Python, avec une signature générique qui le rend réutilisable pour n’importe quel schéma. La contrainte z.ZodType<T> fait le lien entre le schéma passé en argument et le type retourné, si bien que l’appelant récupère un objet correctement typé sans annotation supplémentaire.
import { ZodError } from "zod";
async function safeExtract<T>(
text: string,
schema: z.ZodType<T>,
maxRetries = 2
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await client.chat.parse({
model: "mistral-large-latest",
messages: [
{ role: "system", content: "Extrais les informations demandées." },
{ role: "user", content: text },
],
responseFormat: schema,
temperature: 0,
});
const parsed = response.choices[0].message.parsed;
if (!parsed) {
throw new Error("Réponse parsed est null");
}
return parsed;
} catch (error) {
console.error(`Tentative ${attempt + 1} échouée :`, error);
if (attempt === maxRetries) throw error;
}
}
throw new Error("Échec inattendu");
}
// Utilisation
try {
const book = await safeExtract("Mon texte ici", BookSchema);
console.log(book.title);
} catch (error) {
console.error("Extraction échouée :", error);
}
Points clés à retenir
- Zod est l’équivalent TypeScript de Pydantic pour les Custom Structured Outputs
z.infer<typeof Schema>génère automatiquement le type TypeScript.describe()ajoute des instructions par champ, commeField(description=...)en Python- Le SDK Mistral TypeScript utilise
responseFormat(camelCase) au lieu deresponse_format - La gestion d’erreurs suit le même pattern qu’en Python : retry avec feedback