Extract structured data from LLM responses with Pydantic validation, retry failed extractions automatically, parse complex JSON with type safety, and stream partial results with Instructor - battle-tested structured output library
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Extract structured data from LLM responses with Pydantic validation, retry failed extractions automatically, parse complex JSON with type safety, and stream partial results with Instructor - battle-tested structured output library
# Base installation
pip install instructor
# With specific providers
pip install "instructor[anthropic]"# Anthropic Claude
pip install "instructor[openai]"# OpenAI
pip install "instructor[all]"# All providers
Quick Start
Basic Example: Extract User Data
import instructor
from pydantic import BaseModel
from anthropic import Anthropic
# Define output structureclassUser(BaseModel):
name: str
age: int
email: str# Create instructor client
client = instructor.from_anthropic(Anthropic())
# Extract structured data
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "John Doe is 30 years old. His email is john@example.com"
}],
response_model=User
)
print(user.name) # "John Doe"print(user.age) # 30print(user.email) # "john@example.com"
With OpenAI
from openai import OpenAI
client = instructor.from_openai(OpenAI())
user = client.chat.completions.create(
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "Extract: Alice, 25, alice@email.com"}]
)
Core Concepts
1. Response Models (Pydantic)
Response models define the structure and validation rules for LLM outputs.
Basic Model
from pydantic import BaseModel, Field
classArticle(BaseModel):
title: str = Field(description="Article title")
author: str = Field(description="Author name")
word_count: int = Field(description="Number of words", gt=0)
tags: list[str] = Field(description="List of relevant tags")
article = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Analyze this article: [article text]"
}],
response_model=Article
)
Benefits:
Type safety with Python type hints
Automatic validation (word_count > 0)
Self-documenting with Field descriptions
IDE autocomplete support
Nested Models
classAddress(BaseModel):
street: str
city: str
country: strclassPerson(BaseModel):
name: str
age: int
address: Address # Nested model
person = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "John lives at 123 Main St, Boston, USA"
}],
response_model=Person
)
print(person.address.city) # "Boston"
Optional Fields
from typing importOptionalclassProduct(BaseModel):
name: str
price: float
discount: Optional[float] = None# Optional
description: str = Field(default="No description") # Default value# LLM doesn't need to provide discount or description
Pydantic validates LLM outputs automatically. If validation fails, Instructor retries.
Built-in Validators
from pydantic import Field, EmailStr, HttpUrl
classContact(BaseModel):
name: str = Field(min_length=2, max_length=100)
age: int = Field(ge=0, le=120) # 0 <= age <= 120
email: EmailStr # Validates email format
website: HttpUrl # Validates URL format# If LLM provides invalid data, Instructor retries automatically
Custom Validators
from pydantic import field_validator
classEvent(BaseModel):
name: str
date: str
attendees: int @field_validator('date')defvalidate_date(cls, v):
"""Ensure date is in YYYY-MM-DD format."""import re
ifnot re.match(r'\d{4}-\d{2}-\d{2}', v):
raise ValueError('Date must be YYYY-MM-DD format')
return v
@field_validator('attendees')defvalidate_attendees(cls, v):
"""Ensure positive attendees."""if v < 1:
raise ValueError('Must have at least 1 attendee')
return v
Model-Level Validation
from pydantic import model_validator
classDateRange(BaseModel):
start_date: str
end_date: str @model_validator(mode='after')defcheck_dates(self):
"""Ensure end_date is after start_date."""from datetime import datetime
start = datetime.strptime(self.start_date, '%Y-%m-%d')
end = datetime.strptime(self.end_date, '%Y-%m-%d')
if end < start:
raise ValueError('end_date must be after start_date')
returnself
3. Automatic Retrying
Instructor retries automatically when validation fails, providing error feedback to the LLM.
# Retries up to 3 times if validation fails
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extract user from: John, age unknown"
}],
response_model=User,
max_retries=3# Default is 3
)
# If age can't be extracted, Instructor tells the LLM:# "Validation error: age - field required"# LLM tries again with better extraction
How it works:
LLM generates output
Pydantic validates
If invalid: Error message sent back to LLM
LLM tries again with error feedback
Repeats up to max_retries
4. Streaming
Stream partial results for real-time processing.
Streaming Partial Objects
from instructor import Partial
classStory(BaseModel):
title: str
content: str
tags: list[str]
# Stream partial updates as LLM generatesfor partial_story in client.messages.create_partial(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Write a short sci-fi story"
}],
response_model=Story
):
print(f"Title: {partial_story.title}")
print(f"Content so far: {partial_story.content[:100]}...")
# Update UI in real-time
Streaming Iterables
classTask(BaseModel):
title: str
priority: str# Stream list items as they're generated
tasks = client.messages.create_iterable(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Generate 10 project tasks"
}],
response_model=Task
)
for task in tasks:
print(f"- {task.title} ({task.priority})")
# Process each task as it arrives
Provider Configuration
Anthropic Claude
import instructor
from anthropic import Anthropic
client = instructor.from_anthropic(
Anthropic(api_key="your-api-key")
)
# Use with Claude models
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=YourModel
)
from openai import OpenAI
# Point to local Ollama server
client = instructor.from_openai(
OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"# Required but ignored
),
mode=instructor.Mode.JSON
)
response = client.chat.completions.create(
model="llama3.1",
response_model=YourModel,
messages=[...]
)
Common Patterns
Pattern 1: Data Extraction from Text
classCompanyInfo(BaseModel):
name: str
founded_year: int
industry: str
employees: int
headquarters: str
text = """
Tesla, Inc. was founded in 2003. It operates in the automotive and energy
industry with approximately 140,000 employees. The company is headquartered
in Austin, Texas.
"""
company = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract company information from: {text}"
}],
response_model=CompanyInfo
)
classPerson(BaseModel):
name: str
role: strclassOrganization(BaseModel):
name: str
industry: strclassEntities(BaseModel):
people: list[Person]
organizations: list[Organization]
locations: list[str]
text = "Tim Cook, CEO of Apple, announced at the event in Cupertino..."
entities = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract all entities from: {text}"
}],
response_model=Entities
)
for person in entities.people:
print(f"{person.name} - {person.role}")
Pattern 4: Structured Analysis
classSentimentAnalysis(BaseModel):
overall_sentiment: Sentiment
positive_aspects: list[str]
negative_aspects: list[str]
suggestions: list[str]
score: float = Field(ge=-1.0, le=1.0)
review = "The product works well but setup was confusing..."
analysis = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Analyze this review: {review}"
}],
response_model=SentimentAnalysis
)
Pattern 5: Batch Processing
defextract_person(text: str) -> Person:
return client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract person from: {text}"
}],
response_model=Person
)
texts = [
"John Doe is a 30-year-old engineer",
"Jane Smith, 25, works in marketing",
"Bob Johnson, age 40, software developer"
]
people = [extract_person(text) for text in texts]
Advanced Features
Union Types
from typing importUnionclassTextContent(BaseModel):
type: str = "text"
content: strclassImageContent(BaseModel):
type: str = "image"
url: HttpUrl
caption: strclassPost(BaseModel):
title: str
content: Union[TextContent, ImageContent] # Either type# LLM chooses appropriate type based on content
Dynamic Models
from pydantic import create_model
# Create model at runtime
DynamicUser = create_model(
'User',
name=(str, ...),
age=(int, Field(ge=0)),
email=(EmailStr, ...)
)
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=DynamicUser
)
Custom Modes
# For providers without native structured outputs
client = instructor.from_anthropic(
Anthropic(),
mode=instructor.Mode.JSON # JSON mode
)
# Available modes:# - Mode.ANTHROPIC_TOOLS (recommended for Claude)# - Mode.JSON (fallback)# - Mode.TOOLS (OpenAI tools)
Context Management
# Single-use clientwith instructor.from_anthropic(Anthropic()) as client:
result = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=YourModel
)
# Client closed automatically
Error Handling
Handling Validation Errors
from pydantic import ValidationError
try:
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=User,
max_retries=3
)
except ValidationError as e:
print(f"Failed after retries: {e}")
# Handle gracefullyexcept Exception as e:
print(f"API error: {e}")