> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dottxt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pydantic

> Author JSON Schema with Pydantic models and use it with dottxt structured generation.

[Pydantic](https://docs.pydantic.dev/) is the most common way to author JSON Schemas in Python. You define a model class, Pydantic generates JSON Schema with `model_json_schema()`, and dottxt can enforce that schema through the OpenAI-compatible API.

## Use with dottxt

Pydantic is a good fit when you want Python-native types, schemas defined as code, and runtime validation from the same model definitions.

### Install

<CodeGroup>
  ```bash dottxt SDK theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  pip install dottxt
  ```

  ```bash OpenAI SDK theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  pip install openai pydantic
  ```
</CodeGroup>

### Basic usage

The dottxt SDK accepts a Pydantic model directly as `response_format` and returns a validated instance:

```python Pydantic + dottxt SDK theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from typing import Literal

from pydantic import BaseModel, Field

from dottxt import DotTxt


class IncidentSummary(BaseModel):
    severity: Literal["low", "medium", "high"]
    team: str = Field(max_length=32)


client = DotTxt()
result = client.generate(
    model="openai/gpt-oss-20b",
    input=(
        "Summarize this incident: checkout errors are blocking purchases. "
        "Return a JSON object with keys severity and team."
    ),
    response_format=IncidentSummary,
)
print(result)
# severity='high' team='checkout'
print(result.model_dump())
# {'severity': 'high', 'team': 'checkout'}
```

If you'd rather call the OpenAI-compatible API directly, generate JSON Schema from the model with `model_json_schema()` and send it in `response_format`:

<CodeGroup>
  ```python Pydantic + OpenAI SDK theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  import os
  from openai import OpenAI
  from pydantic import BaseModel, ConfigDict, Field

  class Contact(BaseModel):
      model_config = ConfigDict(extra="forbid")

      name: str = Field(description="Full name")
      email: str = Field(description="Email address")
      role: str | None = Field(default=None, description="Job title")

  client = OpenAI(
      base_url="https://api.dottxt.ai/v1",
      api_key=os.environ["DOTTXT_API_KEY"],
  )

  response = client.chat.completions.create(
      model="openai/gpt-oss-20b",
      messages=[
          {"role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering"}
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "contact",
              "strict": True,
              "schema": Contact.model_json_schema(),
          },
      },
  )

  contact = Contact.model_validate_json(response.choices[0].message.content)

  print(contact.name)
  print(contact.email)
  print(contact.role)
  ```

  ```json JSON Schema (sent to API) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "name": { "type": "string", "description": "Full name" },
      "email": { "type": "string", "description": "Email address" },
      "role": {
        "anyOf": [{ "type": "string" }, { "type": "null" }],
        "default": null,
        "description": "Job title"
      }
    },
    "required": ["name", "email"],
    "additionalProperties": false
  }
  ```

  ```bash curl theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  curl https://api.dottxt.ai/v1/chat/completions \
    -H "Authorization: Bearer $DOTTXT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-oss-20b",
      "messages": [{ "role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering" }],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "contact",
          "schema": {
            "type": "object",
            "properties": {
              "name": { "type": "string", "description": "Full name" },
              "email": { "type": "string", "description": "Email address" },
              "role": {
                "anyOf": [{ "type": "string" }, { "type": "null" }],
                "default": null,
                "description": "Job title"
              }
            },
            "required": ["name", "email"],
            "additionalProperties": false
          }
        }
      }
    }'
  ```
</CodeGroup>

Set `model_config = ConfigDict(extra="forbid")` when you want strict object schemas. Without it, the generated schema allows extra properties.

Pydantic also generates a `title` for fields and models. Those are omitted from the examples below for readability.

### Add constraints and descriptions

Use `Field()` for constraints and descriptions. Use `Literal` for enum values:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Literal
  from pydantic import BaseModel, ConfigDict, Field

  class SupportTicket(BaseModel):
      model_config = ConfigDict(extra="forbid")

      category: Literal["billing", "account", "bug", "feature"] = Field(
          description="The area this ticket relates to."
      )
      priority: Literal["low", "medium", "high"] = Field(
          description="How urgently this ticket needs attention."
      )
      summary: str = Field(
          min_length=10,
          max_length=500,
          description="A brief description of the issue."
      )
      confidence: float = Field(
          ge=0.0,
          le=1.0,
          description="How confident the model is in the classification."
      )
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "category": {
        "type": "string",
        "enum": ["billing", "account", "bug", "feature"],
        "description": "The area this ticket relates to."
      },
      "priority": {
        "type": "string",
        "enum": ["low", "medium", "high"],
        "description": "How urgently this ticket needs attention."
      },
      "summary": {
        "type": "string",
        "minLength": 10,
        "maxLength": 500,
        "description": "A brief description of the issue."
      },
      "confidence": {
        "type": "number",
        "minimum": 0.0,
        "maximum": 1.0,
        "description": "How confident the model is in the classification."
      }
    },
    "required": ["category", "priority", "summary", "confidence"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Descriptions help guide generation, but they are not enforceable constraints like `enum`, `pattern`, or `required`.

## Reference

Use the sections below as a reference for how common Pydantic patterns map to JSON Schema.

### Enums

Use enums when a field must be one of a fixed set of known values.

Use `Literal` when a field should be limited to a fixed set of values:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Literal
  from pydantic import BaseModel, ConfigDict, Field

  class Sentiment(BaseModel):
      model_config = ConfigDict(extra="forbid")

      label: Literal["positive", "negative", "neutral"]
      confidence: float = Field(ge=0.0, le=1.0)
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "label": {
        "type": "string",
        "enum": ["positive", "negative", "neutral"]
      },
      "confidence": {
        "type": "number",
        "minimum": 0.0,
        "maximum": 1.0
      }
    },
    "required": ["label", "confidence"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Python's `enum.Enum` also works. Pydantic puts the enum definition in `$defs` and references it:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from enum import Enum
  from pydantic import BaseModel, ConfigDict

  class Color(str, Enum):
      red = "red"
      green = "green"
      blue = "blue"

  class Palette(BaseModel):
      model_config = ConfigDict(extra="forbid")

      primary: Color
      accent: Color
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "$defs": {
      "Color": {
        "type": "string",
        "enum": ["red", "green", "blue"]
      }
    },
    "type": "object",
    "properties": {
      "primary": { "$ref": "#/$defs/Color" },
      "accent": { "$ref": "#/$defs/Color" }
    },
    "required": ["primary", "accent"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Prefer `Literal` when the values are only used once. Use `Enum` when you want to reuse the same set of values across fields or models.

### Const

Use const-style fields when a value should never vary.

Use a single-value `Literal[...]` when a field must always have one exact value:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Literal
  from pydantic import BaseModel, ConfigDict

  class SearchStep(BaseModel):
      model_config = ConfigDict(extra="forbid")

      action: Literal["search"]
      query: str
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "action": {
        "const": "search",
        "type": "string"
      },
      "query": { "type": "string" }
    },
    "required": ["action", "query"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

### Optional and nullable fields

Use optional and nullable fields carefully because they produce different schema contracts.

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict, Field

  class Lead(BaseModel):
      model_config = ConfigDict(extra="forbid")

      name: str
      email: str
      company: str | None = None
      phone: str | None = None
      notes: str | None = Field(...)
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "email": { "type": "string" },
      "company": {
        "anyOf": [{ "type": "string" }, { "type": "null" }],
        "default": null
      },
      "phone": {
        "anyOf": [{ "type": "string" }, { "type": "null" }],
        "default": null
      },
      "notes": {
        "anyOf": [{ "type": "string" }, { "type": "null" }]
      }
    },
    "required": ["name", "email", "notes"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Fields typed as `T | None` with a default of `None` become nullable and optional. Fields typed as `T | None = Field(...)` stay required but nullable. See [Optional vs Null](/json-schema/optional-vs-null) for the semantic difference.

### Arrays and lists

Use list types for repeated values, then add bounds on the list or its items as needed.

Use `list[T]` for array fields. Pydantic maps list `min_length` and `max_length` to `minItems` and `maxItems`:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict, Field

  class Survey(BaseModel):
      model_config = ConfigDict(extra="forbid")

      question: str
      options: list[str] = Field(min_length=2, max_length=6)
      tags: list[str] = Field(default_factory=list, max_length=5)
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "question": { "type": "string" },
      "options": {
        "type": "array",
        "items": { "type": "string" },
        "minItems": 2,
        "maxItems": 6
      },
      "tags": {
        "type": "array",
        "items": { "type": "string" },
        "maxItems": 5
      }
    },
    "required": ["question", "options"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Setting bounds on arrays prevents the model from generating unbounded lists. See [Bounded Arrays](/json-schema/bounded-arrays) for more.

If you need constraints on each item, put them on the item type:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from typing import Annotated
from pydantic import Field

Tag = Annotated[str, Field(min_length=1, max_length=40)]
tags: list[Tag] = Field(max_length=5)
```

### Formats and specialized types

Use specialized Pydantic types when you want the generated schema to carry semantic format information.

Use Pydantic's built-in types when you want semantic formats in the generated schema:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from datetime import date
  from pydantic import BaseModel, ConfigDict, EmailStr

  class ContactRecord(BaseModel):
      model_config = ConfigDict(extra="forbid")

      email: EmailStr
      signup_date: date
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "email": { "type": "string", "format": "email" },
      "signup_date": { "type": "string", "format": "date" }
    },
    "required": ["email", "signup_date"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Prefer semantic types like `EmailStr` and `date` over plain `str` when you want the schema to carry format information.

### Nested models

Use nested models to reuse object shapes and keep larger schemas maintainable.

Nested models become `$defs` references in the generated schema:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict

  class Address(BaseModel):
      model_config = ConfigDict(extra="forbid")

      street: str
      city: str
      country: str

  class Customer(BaseModel):
      model_config = ConfigDict(extra="forbid")

      name: str
      billing_address: Address
      shipping_address: Address
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "$defs": {
      "Address": {
        "type": "object",
        "properties": {
          "street": { "type": "string" },
          "city": { "type": "string" },
          "country": { "type": "string" }
        },
        "required": ["street", "city", "country"],
        "additionalProperties": false
      }
    },
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "billing_address": { "$ref": "#/$defs/Address" },
      "shipping_address": { "$ref": "#/$defs/Address" }
    },
    "required": ["name", "billing_address", "shipping_address"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

### Discriminated unions

Use discriminated unions when the output can take one of several object shapes.

Use `Literal` with `Field(discriminator=...)` to generate tagged `oneOf` schemas in Pydantic:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Literal
  from pydantic import BaseModel, ConfigDict, Field

  class SearchAction(BaseModel):
      model_config = ConfigDict(extra="forbid")

      action: Literal["search"]
      query: str

  class LookupAction(BaseModel):
      model_config = ConfigDict(extra="forbid")

      action: Literal["lookup"]
      id: int

  class AgentOutput(BaseModel):
      model_config = ConfigDict(extra="forbid")

      step: SearchAction | LookupAction = Field(discriminator="action")
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "$defs": {
      "SearchAction": {
        "type": "object",
        "properties": {
          "action": { "const": "search", "type": "string" },
          "query": { "type": "string" }
        },
        "required": ["action", "query"],
        "additionalProperties": false
      },
      "LookupAction": {
        "type": "object",
        "properties": {
          "action": { "const": "lookup", "type": "string" },
          "id": { "type": "integer" }
        },
        "required": ["action", "id"],
        "additionalProperties": false
      }
    },
    "type": "object",
    "properties": {
      "step": {
        "oneOf": [
          { "$ref": "#/$defs/SearchAction" },
          { "$ref": "#/$defs/LookupAction" }
        ],
        "discriminator": {
          "propertyName": "action",
          "mapping": {
            "search": "#/$defs/SearchAction",
            "lookup": "#/$defs/LookupAction"
          }
        }
      }
    },
    "required": ["step"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

Pydantic emits `discriminator` metadata in the generated schema, but the important part for dottxt is the `oneOf` structure and the `const` tag values on each branch. That is what makes the output unambiguous at generation time.

See [AnyOf Object Variants](/json-schema/anyof-object-variants) for the schema design side of this pattern.

### Recursive models

Use recursive models for trees and other nested structures where items can contain more items of the same shape.

Models that reference themselves produce recursive `$defs`:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from __future__ import annotations
  from pydantic import BaseModel, ConfigDict, Field

  class TreeNode(BaseModel):
      model_config = ConfigDict(extra="forbid")

      label: str
      children: list[TreeNode] = Field(default_factory=list, max_length=10)

  class TreeResponse(BaseModel):
      model_config = ConfigDict(extra="forbid")

      tree: TreeNode
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "$defs": {
      "TreeNode": {
        "type": "object",
        "properties": {
          "label": { "type": "string" },
          "children": {
            "type": "array",
            "items": { "$ref": "#/$defs/TreeNode" },
            "maxItems": 10
          }
        },
        "required": ["label"],
        "additionalProperties": false
      }
    },
    "type": "object",
    "properties": {
      "tree": { "$ref": "#/$defs/TreeNode" }
    },
    "required": ["tree"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

`from __future__ import annotations` enables forward references so the model can reference itself. Set bounds on recursive lists so generation does not expand without limit.

Keep the recursive type under a named object property rather than using the recursive node itself as the top-level response schema.

### Composition and inheritance

Use inheritance to combine shared field groups without repeating schema definitions by hand.

Use multiple inheritance to combine reusable field groups:

<CodeGroup>
  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict

  class Timestamped(BaseModel):
      created_at: str
      updated_at: str

  class Authored(BaseModel):
      author: str

  class Article(Timestamped, Authored):
      model_config = ConfigDict(extra="forbid")

      title: str
      body: str
  ```

  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "created_at": { "type": "string" },
      "updated_at": { "type": "string" },
      "author": { "type": "string" },
      "title": { "type": "string" },
      "body": { "type": "string" }
    },
    "required": ["created_at", "updated_at", "author", "title", "body"],
    "additionalProperties": false
  }
  ```
</CodeGroup>

### Validators do not affect schema

Use validators for application-side checks, but do not rely on them to shape the generated schema.

Pydantic validators run at parse time, but they do not appear in the generated JSON Schema. If you need to constrain generation, express it in the type annotation or `Field()`:

<CodeGroup>
  ```python Won't constrain generation theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from pydantic import BaseModel, ConfigDict, field_validator

  class Invoice(BaseModel):
      model_config = ConfigDict(extra="forbid")

      amount: float
      currency: str

      @field_validator("currency")
      @classmethod
      def currency_must_be_valid(cls, v: str) -> str:
          if v not in ("USD", "EUR", "GBP"):
              raise ValueError("unsupported currency")
          return v
  ```

  ```python Constrains generation theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Literal
  from pydantic import BaseModel, ConfigDict

  class Invoice(BaseModel):
      model_config = ConfigDict(extra="forbid")

      amount: float
      currency: Literal["USD", "EUR", "GBP"]
  ```
</CodeGroup>

## Notes

* Use Pydantic when you want Python types, runtime validation, and JSON Schema generation from one model definition. Use raw JSON Schema directly when you need full control over the output shape or keywords that do not map cleanly from Pydantic types.
* Set `ConfigDict(extra="forbid")` when you want `additionalProperties: false`.
* Use `Literal` for enums rather than `json_schema_extra={"enum": [...]}`.
* Use validators for parse-time checks, not generation-time constraints.
* See [String Bounds](/json-schema/string-bounds), [Bounded Arrays](/json-schema/bounded-arrays), [AnyOf Object Variants](/json-schema/anyof-object-variants), and [Optional vs Null](/json-schema/optional-vs-null) for schema design details that matter during generation.
