Instructor
Instructor
Section titled “Instructor”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
Skill metadata
Section titled “Skill metadata”| Source | Optional — install with hermes skills install official/mlops/instructor |
| Path | optional-skills/mlops/instructor |
| Version | 1.0.0 |
| Author | Orchestra Research |
| License | MIT |
| Dependencies | instructor, pydantic, openai, anthropic |
| Tags | Prompt Engineering, Instructor, Structured Output, Pydantic, Data Extraction, JSON Parsing, Type Safety, Validation, Streaming, OpenAI, Anthropic |
Reference: full SKILL.md
Section titled “Reference: full SKILL.md”The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
Instructor: Structured LLM Outputs
Section titled “Instructor: Structured LLM Outputs”When to Use This Skill
Section titled “When to Use This Skill”Use Instructor when you need to:
- Extract structured data from LLM responses reliably
- Validate outputs against Pydantic schemas automatically
- Retry failed extractions with automatic error handling
- Parse complex JSON with type safety and validation
- Stream partial results for real-time processing
- Support multiple LLM providers with consistent API
GitHub Stars: 15,000+ | Battle-tested: 100,000+ developers
Installation
Section titled “Installation”# Base installationpip install instructor
# With specific providerspip install "instructor[anthropic]" # Anthropic Claudepip install "instructor[openai]" # OpenAIpip install "instructor[all]" # All providersQuick Start
Section titled “Quick Start”Basic Example: Extract User Data
Section titled “Basic Example: Extract User Data”import instructorfrom pydantic import BaseModelfrom anthropic import Anthropic
# Define output structureclass User(BaseModel): name: str age: int email: str
# Create instructor clientclient = instructor.from_anthropic(Anthropic())
# Extract structured datauser = 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
Section titled “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
Section titled “Core Concepts”1. Response Models (Pydantic)
Section titled “1. Response Models (Pydantic)”Response models define the structure and validation rules for LLM outputs.
Basic Model
Section titled “Basic Model”from pydantic import BaseModel, Field
class Article(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
Section titled “Nested Models”class Address(BaseModel): street: str city: str country: str
class Person(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
Section titled “Optional Fields”from typing import Optional
class Product(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 descriptionEnums for Constraints
Section titled “Enums for Constraints”from enum import Enum
class Sentiment(str, Enum): POSITIVE = "positive" NEGATIVE = "negative" NEUTRAL = "neutral"
class Review(BaseModel): text: str sentiment: Sentiment # Only these 3 values allowed
review = client.messages.create( model="claude-sonnet-4-5-20250929", max_tokens=1024, messages=[{ "role": "user", "content": "This product is amazing!" }], response_model=Review)
print(review.sentiment) # Sentiment.POSITIVE2. Validation
Section titled “2. Validation”Pydantic validates LLM outputs automatically. If validation fails, Instructor retries.
Built-in Validators
Section titled “Built-in Validators”from pydantic import Field, EmailStr, HttpUrl
class Contact(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 automaticallyCustom Validators
Section titled “Custom Validators”from pydantic import field_validator
class Event(BaseModel): name: str date: str attendees: int
@field_validator('date') def validate_date(cls, v): """Ensure date is in YYYY-MM-DD format.""" import re if not re.match(r'\d{4}-\d{2}-\d{2}', v): raise ValueError('Date must be YYYY-MM-DD format') return v
@field_validator('attendees') def validate_attendees(cls, v): """Ensure positive attendees.""" if v < 1: raise ValueError('Must have at least 1 attendee') return vModel-Level Validation
Section titled “Model-Level Validation”from pydantic import model_validator
class DateRange(BaseModel): start_date: str end_date: str
@model_validator(mode='after') def check_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') return self3. Automatic Retrying
Section titled “3. Automatic Retrying”Instructor retries automatically when validation fails, providing error feedback to the LLM.
# Retries up to 3 times if validation failsuser = 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 extractionHow 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
Section titled “4. Streaming”Stream partial results for real-time processing.
Streaming Partial Objects
Section titled “Streaming Partial Objects”from instructor import Partial
class Story(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-timeStreaming Iterables
Section titled “Streaming Iterables”class Task(BaseModel): title: str priority: str
# Stream list items as they're generatedtasks = 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 arrivesProvider Configuration
Section titled “Provider Configuration”Anthropic Claude
Section titled “Anthropic Claude”import instructorfrom anthropic import Anthropic
client = instructor.from_anthropic( Anthropic(api_key="your-api-key"))
# Use with Claude modelsresponse = client.messages.create( model="claude-sonnet-4-5-20250929", max_tokens=1024, messages=[...], response_model=YourModel)OpenAI
Section titled “OpenAI”from openai import OpenAI
client = instructor.from_openai( OpenAI(api_key="your-api-key"))
response = client.chat.completions.create( model="gpt-4o-mini", response_model=YourModel, messages=[...])Local Models (Ollama)
Section titled “Local Models (Ollama)”from openai import OpenAI
# Point to local Ollama serverclient = 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
Section titled “Common Patterns”Pattern 1: Data Extraction from Text
Section titled “Pattern 1: Data Extraction from Text”class CompanyInfo(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 energyindustry with approximately 140,000 employees. The company is headquarteredin 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)Pattern 2: Classification
Section titled “Pattern 2: Classification”class Category(str, Enum): TECHNOLOGY = "technology" FINANCE = "finance" HEALTHCARE = "healthcare" EDUCATION = "education" OTHER = "other"
class ArticleClassification(BaseModel): category: Category confidence: float = Field(ge=0.0, le=1.0) keywords: list[str]
classification = client.messages.create( model="claude-sonnet-4-5-20250929", max_tokens=1024, messages=[{ "role": "user", "content": "Classify this article: [article text]" }], response_model=ArticleClassification)Pattern 3: Multi-Entity Extraction
Section titled “Pattern 3: Multi-Entity Extraction”class Person(BaseModel): name: str role: str
class Organization(BaseModel): name: str industry: str
class Entities(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
Section titled “Pattern 4: Structured Analysis”class SentimentAnalysis(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
Section titled “Pattern 5: Batch Processing”def extract_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
Section titled “Advanced Features”Union Types
Section titled “Union Types”from typing import Union
class TextContent(BaseModel): type: str = "text" content: str
class ImageContent(BaseModel): type: str = "image" url: HttpUrl caption: str
class Post(BaseModel): title: str content: Union[TextContent, ImageContent] # Either type
# LLM chooses appropriate type based on contentDynamic Models
Section titled “Dynamic Models”from pydantic import create_model
# Create model at runtimeDynamicUser = 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
Section titled “Custom Modes”# For providers without native structured outputsclient = 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
Section titled “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 automaticallyError Handling
Section titled “Error Handling”Handling Validation Errors
Section titled “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 gracefully
except Exception as e: print(f"API error: {e}")Custom Error Messages
Section titled “Custom Error Messages”class ValidatedUser(BaseModel): name: str = Field(description="Full name, 2-100 characters") age: int = Field(description="Age between 0 and 120", ge=0, le=120) email: EmailStr = Field(description="Valid email address")
class Config: # Custom error messages json_schema_extra = { "examples": [ { "name": "John Doe", "age": 30, "email": "john@example.com" } ] }Best Practices
Section titled “Best Practices”1. Clear Field Descriptions
Section titled “1. Clear Field Descriptions”# ❌ Bad: Vagueclass Product(BaseModel): name: str price: float
# ✅ Good: Descriptiveclass Product(BaseModel): name: str = Field(description="Product name from the text") price: float = Field(description="Price in USD, without currency symbol")2. Use Appropriate Validation
Section titled “2. Use Appropriate Validation”# ✅ Good: Constrain valuesclass Rating(BaseModel): score: int = Field(ge=1, le=5, description="Rating from 1 to 5 stars") review: str = Field(min_length=10, description="Review text, at least 10 chars")3. Provide Examples in Prompts
Section titled “3. Provide Examples in Prompts”messages = [{ "role": "user", "content": """Extract person info from: "John, 30, engineer"
Example format:{ "name": "John Doe", "age": 30, "occupation": "engineer"}"""}]4. Use Enums for Fixed Categories
Section titled “4. Use Enums for Fixed Categories”# ✅ Good: Enum ensures valid valuesclass Status(str, Enum): PENDING = "pending" APPROVED = "approved" REJECTED = "rejected"
class Application(BaseModel): status: Status # LLM must choose from enum5. Handle Missing Data Gracefully
Section titled “5. Handle Missing Data Gracefully”class PartialData(BaseModel): required_field: str optional_field: Optional[str] = None default_field: str = "default_value"
# LLM only needs to provide required_fieldComparison to Alternatives
Section titled “Comparison to Alternatives”| Feature | Instructor | Manual JSON | LangChain | DSPy |
|---|---|---|---|---|
| Type Safety | ✅ Yes | ❌ No | ⚠️ Partial | ✅ Yes |
| Auto Validation | ✅ Yes | ❌ No | ❌ No | ⚠️ Limited |
| Auto Retry | ✅ Yes | ❌ No | ❌ No | ✅ Yes |
| Streaming | ✅ Yes | ❌ No | ✅ Yes | ❌ No |
| Multi-Provider | ✅ Yes | ⚠️ Manual | ✅ Yes | ✅ Yes |
| Learning Curve | Low | Low | Medium | High |
When to choose Instructor:
- Need structured, validated outputs
- Want type safety and IDE support
- Require automatic retries
- Building data extraction systems
When to choose alternatives:
- DSPy: Need prompt optimization
- LangChain: Building complex chains
- Manual: Simple, one-off extractions
Resources
Section titled “Resources”- Documentation: https://python.useinstructor.com
- GitHub: https://github.com/jxnl/instructor (15k+ stars)
- Cookbook: https://python.useinstructor.com/examples
- Discord: Community support available
See Also
Section titled “See Also”references/validation.md- Advanced validation patternsreferences/providers.md- Provider-specific configurationreferences/examples.md- Real-world use cases