Skip to main content
Lesson 6 of 12 · Structured JSON Outputs

Pydantic BaseModel in Depth

Lesson 6 of 12 Updated July 30, 2026 Developer
On this page
  1. Pydantic: The Ideal Partner
  2. Pydantic Basics for Structured Outputs
  3. Supported Python Types
  4. Nested Models
  5. Enums with Pydantic
  6. Using parse()
  7. Using response_format
  8. Default Values and Documentation
  9. Key Takeaways

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
  • Literal replaces enum to enforce predefined values
  • str | None makes a field optional (can be null)
  • Field(description=...) improves the quality of the model’s responses
  • Works with parse() (automatic) and response_format (manual)