instructorpydanticstructured-outputvalidationai

Instructor: Guaranteed JSON from LLMs with Pydantic and Auto-Retries

·8 min read·AI & JSON

The library that made "just retry" a feature

Instructor is a small, widely used library for extracting structured, validated data from LLMs. Its whole idea fits in one sentence: define your output as a Pydantic model, and Instructor patches the LLM client to enforce that model — automatically validating the response and retrying on failure by sending the validation error back to the model. It supports 15+ providers (OpenAI, Anthropic, Gemini, Ollama, DeepSeek…) and ships in Python, TypeScript, Go, Ruby, and more.

The core pattern

You patch the client once, then pass response_model:

python
import instructor
from pydantic import BaseModel, field_validator
from openai import OpenAI

class User(BaseModel):
    name: str
    age: int

    @field_validator("age")
    @classmethod
    def sane_age(cls, v: int) -> int:
        if not 0 < v < 130:
            raise ValueError("age must be between 1 and 129")
        return v

client = instructor.from_openai(OpenAI())

user = client.chat.completions.create(
    model="gpt-5.2",
    response_model=User,
    messages=[{"role": "user", "content": "Extract: Ada is forty-two years old."}],
)
# user is a validated User instance -> User(name="Ada", age=42)

Why the retry loop is the killer feature

Native structured outputs guarantee the *shape*. They can't guarantee your *rules* — that an age is plausible, that a total equals the line items, that a category is one you actually support. Those are Pydantic validators. When a validator raises ValueError, Instructor catches it, appends the error message to the conversation, and asks the model to try again:

text
1. Model returns { "name": "Ada", "age": 942 }
2. Validator raises: "age must be between 1 and 129"
3. Instructor re-prompts the model with that exact error
4. Model corrects to { "name": "Ada", "age": 42 }

You set max_retries, and self-correction happens without a single line of glue code. This is what turns "the model usually gets it right" into "the model's output is production-safe."

Streaming and nested objects

Instructor also supports streaming partial objects (populate a UI as fields arrive) and arbitrarily nested models — a Report containing a list of Section objects each with typed fields — validated as a whole. Because it's just Pydantic, everything you know about models, defaults, and validators carries over.

Fitting it into a JSON workflow

The Pydantic model is your contract, so get it right before wiring in the LLM:

  1. Prototype a realistic response and format it to confirm the shape.
  2. Convert the sample to Python to scaffold the model, or generate a JSON Schema to reason about required vs. optional fields.
  3. Keep golden examples and diff new extractions against them when you change prompts or models.

Instructor sits in the same family as Pydantic AI and the Vercel AI SDK; for the fundamentals see Validate LLM JSON Against a Schema.

Frequently asked questions

Native structured outputs enforce the JSON shape at the provider level. Instructor adds a validation-and-retry loop on top: your Pydantic validators enforce business rules, and when one fails, Instructor feeds the error back to the model and retries — catching values that are well-formed but wrong.

15+, including OpenAI, Anthropic, Google Gemini, Ollama, and DeepSeek, with client libraries in Python, TypeScript, Go, Ruby, and others. The same response_model pattern works across them.

Instructor retries up to max_retries, each time including the validation error. If it still can't produce a valid object, it raises — so you can fail loudly rather than store bad data.

Yes. Instructor supports Ollama, so you get the same validated-Pydantic-with-retries pattern against local models — pairing well with Ollama's own constrained-decoding format parameter.

Try JSON to Python

Generate the Pydantic model Instructor validates against from a sample.