TL;DR:
- Instructor wraps any OpenAI-compatible API client and adds automatic Pydantic validation with retry logic — you define the output schema as a Pydantic model, and Instructor ensures the LLM response matches it
- Works with OpenAI, Anthropic, Gemini, Mistral, Ollama, and most other providers via their respective client patches
- The main value over native structured outputs: retry-on-validation-error, streaming partial models, and a unified interface across providers
The Problem Instructor Solves
Getting structured data out of an LLM involves a recurring set of concerns:
- The model should return JSON conforming to a specific schema
- The JSON might be malformed or missing required fields
- You want to retry with the validation error context if the first attempt fails
- You want to validate and deserialise the output into a typed Python object
Doing this manually is repetitive. Native structured output APIs (OpenAI’s response_format, Anthropic’s tool use) handle the schema enforcement but vary in interface and don’t give you integrated retry logic or Pydantic model deserialisation.
Instructor wraps these primitives and gives you a consistent, Pydantic-native interface regardless of which provider you’re using.
Installation and Basic Usage
pip install instructor
The basic pattern:
import instructor
from openai import OpenAI
from pydantic import BaseModel
# Patch the client
client = instructor.from_openai(OpenAI())
class UserProfile(BaseModel):
name: str
age: int
occupation: str
profile = client.chat.completions.create(
model="gpt-4o",
response_model=UserProfile,
messages=[
{"role": "user", "content": "Extract: John is a 34-year-old software engineer."}
]
)
print(profile.name) # John
print(profile.age) # 34
print(profile.occupation) # software engineer
profile is a UserProfile instance — a proper typed Python object, not a dict or raw string. The JSON parsing, schema enforcement, and deserialisation happen automatically.
How It Works
Instructor uses mode-specific strategies depending on the provider:
- OpenAI and compatible APIs: Uses
response_formatwith JSON schema (or function calling in older models). The Pydantic model’s JSON schema is passed to the API. The response is validated against the model. - Anthropic: Uses tool use (function calling). Anthropic’s API doesn’t support JSON schema natively in
response_format, so Instructor encodes the schema as a tool definition. - Other providers: Instructor has specific patches for Gemini, Cohere, Mistral, Ollama, LiteLLM, and others. The strategy varies by what the provider supports.
The from_openai, from_anthropic, from_gemini (etc.) constructors return a patched client that intercepts calls where response_model is specified.
Validation and Retries
The core feature that goes beyond just JSON parsing is validation with automatic retry:
from pydantic import BaseModel, field_validator
import instructor
from openai import OpenAI
client = instructor.from_openai(OpenAI())
class ExtractedDate(BaseModel):
year: int
month: int
day: int
@field_validator("month")
@classmethod
def month_in_range(cls, v):
if not 1 <= v <= 12:
raise ValueError(f"Month must be 1-12, got {v}")
return v
date = client.chat.completions.create(
model="gpt-4o",
response_model=ExtractedDate,
max_retries=3, # Retry up to 3 times on validation error
messages=[
{"role": "user", "content": "Extract the date: The event is on the fourteenth of March 2025."}
]
)
If the model returns {"year": 2025, "month": 14, "day": 3} — which would fail the validator — Instructor sends the validation error back to the model with a message like “Validation error: Month must be 1-12, got 14. Please fix and return valid JSON.” The model corrects the response. This happens up to max_retries times before raising an exception.
This retry-on-validation-failure pattern is where Instructor earns its keep in production. Models make extraction errors, especially on edge cases. Having the validation error fed back into context for self-correction dramatically reduces the rate of downstream failures.
Nested and Complex Models
Pydantic models compose naturally for complex extraction:
from pydantic import BaseModel
from typing import List, Optional
import instructor
from openai import OpenAI
client = instructor.from_openai(OpenAI())
class Address(BaseModel):
street: str
city: str
postcode: str
class ContactRecord(BaseModel):
name: str
email: Optional[str] = None
phone: Optional[str] = None
addresses: List[Address] = []
record = client.chat.completions.create(
model="gpt-4o",
response_model=ContactRecord,
messages=[{
"role": "user",
"content": """Extract contact info:
Sarah Chen, sarah@example.com, +44 7700 900123
Home: 12 Baker Street, London, W1U 6TN
Work: 45 Canary Wharf, London, E14 5AB"""
}]
)
for addr in record.addresses:
print(addr.city) # London, London
Nested models, lists, optionals, and enums all work because Instructor just uses Pydantic’s JSON schema generation.
Streaming Partial Models
For long extraction tasks, streaming lets you process results as they arrive:
from instructor import Partial
# Stream a partial model as tokens arrive
for partial_record in client.chat.completions.create_partial(
model="gpt-4o",
response_model=ContactRecord,
messages=[{"role": "user", "content": "...long document..."}]
):
if partial_record.name:
print(f"Name found: {partial_record.name}")
# Fields are None until the model generates them
Partial[Model] variants allow processing fields as soon as they’re available rather than waiting for the complete response. This is useful when displaying results progressively or when early fields can trigger downstream work.
Multi-Provider Usage
import instructor
from anthropic import Anthropic
from openai import OpenAI
from pydantic import BaseModel
class Summary(BaseModel):
headline: str
key_points: list[str]
sentiment: str
# OpenAI
oai_client = instructor.from_openai(OpenAI())
summary_oai = oai_client.chat.completions.create(
model="gpt-4o",
response_model=Summary,
messages=[{"role": "user", "content": "Summarise: ..."}]
)
# Anthropic — same model, same response
ant_client = instructor.from_anthropic(Anthropic())
summary_ant = ant_client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
response_model=Summary,
messages=[{"role": "user", "content": "Summarise: ..."}]
)
Both return a Summary instance. The client interface differs (Anthropic’s messages.create vs OpenAI’s chat.completions.create) but the response_model and return type are the same.
Comparison to Native Structured Outputs
OpenAI structured outputs (response_format={"type": "json_schema", "json_schema": {...}}) natively enforce schema compliance at the API level — the model is constrained to generate tokens that conform to the schema. This is more reliable than prompt-based JSON generation. Instructor can use this mode when patched with instructor.from_openai(client, mode=instructor.Mode.JSON_SCHEMA).
Anthropic tool use similarly enforces JSON via constrained generation.
What Instructor adds on top:
- Unified interface: Same code works across providers
- Retry logic: Validation failures get sent back to the model automatically
- Pydantic integration: Custom validators, computed fields, and model methods work
- Streaming partial models: Not available in raw structured output APIs
For simple extraction where the native API’s JSON mode suffices, Instructor’s overhead may not be worth it. For production pipelines where you need retries, multi-provider support, or complex validation logic, it saves significant boilerplate.
Common Use Cases in Agentic Workflows
Entity extraction from documents: Pull structured records (people, dates, organisations, amounts) from unstructured text at scale.
Classification with confidence: Return an enum field plus a confidence score, with validation ensuring the score is in range.
Tool call response parsing: When building agents that call external tools, parse the tool’s string output into a structured model before passing to the next step.
Data normalisation pipelines: Convert inconsistent formats (date strings, address formats, currency) into normalised schema-validated records.
Evaluation harnesses: Use structured models to capture evaluation outputs (pass/fail, score, reasoning) in a form that’s easy to aggregate and analyse.
Instructor’s GitHub repository includes a comprehensive cookbook with patterns for each of these cases, along with provider-specific examples and integration guides.