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:
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:
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:
- Prototype a realistic response and format it to confirm the shape.
- Convert the sample to Python to scaffold the model, or generate a JSON Schema to reason about required vs. optional fields.
- 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.