Schémas Zod en TypeScript
Zod : l’équivalent TypeScript de Pydantic
Si vous travaillez en TypeScript (ou 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.
Installer Zod et le SDK Mistral
npm install @mistralai/mistralai zod
Zod fonctionne nativement avec TypeScript et fournit une inférence de types automatique.
Définir un schéma simple
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 }
Types Zod courants
Zod supporte tous les types nécessaires pour les sorties structurées :
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>;
Correspondance Pydantic / Zod
| 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, vous pouvez imbriquer des schémas :
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>;
Exemple complet avec le SDK Mistral
Voici un exemple complet d’extraction structurée en TypeScript :
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();
Descriptions de champs avec .describe()
Comme Field(description=...) en Pydantic, utilisez .describe() en Zod pour guider le modèle :
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 en TypeScript
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