Pydantic BaseModel in Depth
On this page
Pydantic: The Ideal Partner
Pydantic is the standard Python library for data validation. With Grok’s structured outputs, it becomes an essential tool: you define your schemas as Python classes, and the model populates these classes automatically.
Pydantic Basics for Structured Outputs
You create a Pydantic model by inheriting from BaseModel. Every attribute with a type annotation becomes a field in the JSON schema:
from pydantic import BaseModel
class Produit(BaseModel):
nom: str
prix: float
en_stock: bool
categorie: str
This model automatically generates the equivalent JSON schema. You don’t need to write anything else.
Supported Python Types
Here is the mapping between Python types and JSON Schema types:
class ExempleComplet(BaseModel):
# Scalars
texte: str # → "type": "string"
nombre: float # → "type": "number"
entier: int # → "type": "number"
actif: bool # → "type": "boolean"
# Optionals
note: str | None # → anyOf string/null
# Collections
tags: list[str] # → array of strings
scores: list[float] # → array of numbers
# Enumerations
niveau: str # + enum in Field
Nested Models
The real power of Pydantic lies in nesting models:
class Adresse(BaseModel):
rue: str
ville: str
code_postal: str
pays: str
class Competence(BaseModel):
nom: str
niveau: str # "debutant", "intermediaire", "expert"
annees: int
class Candidat(BaseModel):
prenom: str
nom: str
email: str
adresse: Adresse
competences: list[Competence]
disponible: bool
pretention_salariale: float | None
When you pass Candidat as the response_format, the model will fill the entire hierarchy: the complete address, each skill with its details, and so on.
Enums with Pydantic
To enforce predefined values, use Python’s Literal:
from typing import Literal
class Ticket(BaseModel):
titre: str
description: str
priorite: Literal["basse", "moyenne", "haute", "critique"]
statut: Literal["ouvert", "en_cours", "resolu", "ferme"]
type: Literal["bug", "feature", "documentation"]
The model can only return the listed values. Any other value is impossible.
Using parse()
response, ticket = client.chat.parse(
model="grok-4.20-0309-reasoning",
messages=[{
"role": "user",
"content": "The login button does not work on mobile"
}],
response_format=Ticket
)
print(ticket.priorite) # "haute"
print(ticket.type) # "bug"
print(ticket.statut) # "ouvert"
Using response_format
response = client.chat.create(
model="grok-4.20-0309-reasoning",
messages=[{
"role": "user",
"content": "The login button does not work on mobile"
}],
response_format=Ticket
)
ticket = Ticket.model_validate_json(
response.choices[0].message.content
)
Default Values and Documentation
You can add descriptions to your fields with Field. These descriptions help the model better understand what you expect:
from pydantic import BaseModel, Field
class AnalyseSentiment(BaseModel):
texte_original: str = Field(
description="The text analyzed as is"
)
sentiment: Literal["positif", "negatif", "neutre"] = Field(
description="The dominant sentiment of the text"
)
score_confiance: float = Field(
description="Confidence score between 0.0 and 1.0"
)
mots_cles: list[str] = Field(
description="The 3 to 5 most significant keywords"
)
The descriptions in Field are included in the JSON schema sent to the model. They improve the quality of the responses.
Key Takeaways
- Pydantic automatically converts your Python classes into JSON schemas
- Nesting models allows you to structure complex data
Literalreplacesenumto enforce predefined valuesstr | Nonemakes a field optional (can benull)Field(description=...)improves the quality of the model’s responses- Works with
parse()(automatic) andresponse_format(manual)