Define Schemas with Pydantic
On this page
Pydantic: The Foundation of Custom Structured Outputs in Python
Pydantic is the most popular data validation library in Python. It lets you define data models with strict types, which is exactly what the Mistral API uses for Custom Structured Outputs. If you work in Python, Pydantic becomes your primary tool for describing output schemas: what you write in a class is what the model will be constrained to produce.
Here’s the good news on installation: Pydantic is included as a dependency of the Mistral SDK. If you already have mistralai installed, you don’t need to do anything else.
pip install mistralai
# Pydantic v2 is included automatically
A quick version check avoids surprises, since validator syntax changed between major versions:
import pydantic
print(pydantic.__version__) # 2.x required
Define a Simple Model
A Pydantic model is a class that inherits from BaseModel, where each attribute is a field with an explicit type.
from pydantic import BaseModel
class Contact(BaseModel):
name: str
email: str
phone: str
company: str
This model tells Mistral: “Your output must be a JSON object with exactly these 4 fields, all of type string.” No additional instructions are needed in the prompt to get this structure.
Types You Will Use
Pydantic supports all standard Python types, and Mistral understands them all. The following model brings them together in a realistic case—analyzing a support ticket—and shows how they combine: basic types for simple fields, Optional for an assignee not yet designated, Enum for a priority that can only take four values, and lists for tags and related IDs.
from pydantic import BaseModel
from typing import Optional
from enum import Enum
class Priority(str, Enum):
low = "low"
medium = "medium"
high = "high"
critical = "critical"
class TicketAnalysis(BaseModel):
# Basic types
title: str
description: str
ticket_id: int
confidence: float
is_urgent: bool
# Optional types
assignee: Optional[str] = None
# Enums (constrained values)
priority: Priority
# Lists
tags: list[str]
related_ids: list[int]
Here is the corresponding cheat sheet:
| Type | Meaning |
|---|---|
str | string |
int | integer |
float | decimal number |
bool | boolean (true/false) |
list[T] | list of elements of type T |
Optional[T] | field that can be null |
Enum | value from a finite set |
Enums deserve special mention: they are the only way to guarantee that a priority will never be "very urgent" or "P1" depending on the model’s mood.
Nested Models
Real-world data is rarely flat. A contact belongs to a company, which has an address. Pydantic lets you nest models as deeply as needed, and Mistral follows the hierarchy.
from pydantic import BaseModel
from typing import Optional
class Address(BaseModel):
street: str
city: str
postal_code: str
country: str
class Company(BaseModel):
name: str
industry: str
address: Address # Nested model
class Person(BaseModel):
first_name: str
last_name: str
email: str
role: str
company: Company # Two-level nesting
The generated JSON reproduces this tree structure exactly:
{
"first_name": "Marie",
"last_name": "Dupont",
"email": "[email protected]",
"role": "Sales Director",
"company": {
"name": "TechVision SAS",
"industry": "Technology",
"address": {
"street": "42 rue de la Paix",
"city": "Paris",
"postal_code": "75002",
"country": "France"
}
}
}
The same mechanism extracts multiple items at once: a model wraps a list of another model, and you retrieve a complete catalog in a single call.
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
category: str
in_stock: bool
class Catalog(BaseModel):
products: list[Product]
total_count: int
Guide the Model with Descriptions
A field name doesn’t tell the whole story. score could mean a grade out of 10, a percentage, or a value between 0 and 1—the model has to guess. Descriptions remove this ambiguity: they are included in the JSON schema sent to Mistral and act as per-field instructions.
from pydantic import BaseModel, Field
class SentimentAnalysis(BaseModel):
sentiment: str = Field(
description="Overall sentiment: 'positive', 'negative', or 'neutral'"
)
score: float = Field(
description="Confidence score between 0.0 and 1.0"
)
keywords: list[str] = Field(
description="The 3-5 main keywords from the text"
)
summary: str = Field(
description="Summary in one sentence maximum"
)
The more precise these descriptions are, the better the output quality. This is the highest-leverage part of the whole setup: a few well-chosen words often replace an entire paragraph of prompt.
Field also sets default values. In the next example, the language defaults to "fr" if the model doesn’t find the information, the word count stays None if it can’t be determined, and tags start as an empty list thanks to default_factory.
from pydantic import BaseModel, Field
from typing import Optional
class ArticleMetadata(BaseModel):
title: str
author: str
language: str = Field(default="fr", description="ISO 639-1 language code")
word_count: Optional[int] = None
tags: list[str] = Field(default_factory=list)
Fields with default values will be filled by the model if it finds the information, or will keep the default value otherwise.
Validation Comes for Free
Last but not least: validation is automatic. If a value doesn’t match the declared type, Pydantic immediately raises a ValidationError indicating exactly which field is at fault, without you writing a single line of checking code.
from pydantic import ValidationError
try:
analysis = SentimentAnalysis(
sentiment="positive",
score="not a number", # Type error
keywords=["test"],
summary="Summary"
)
except ValidationError as e:
print(e)
# 1 validation error for SentimentAnalysis
# score
# Input should be a valid number [type=float_parsing, ...]
All the manual verification code written in the previous lesson disappears here, replaced by the model declaration.
Key Takeaways
- Pydantic
BaseModelis the basis of all Custom schemas in Python - Use explicit types:
str,int,float,bool,list[T],Optional[T] - Nested models enable complex JSON structures
Field(description=...)improves output quality by guiding the model- Validation is automatic—type errors are detected immediately