instructor

Structured LLM outputs validated with Pydantic.

  • Prompt Engineering
  • Instructor
  • Structured Output
  • Pydantic
  • Data Extraction
  • JSON Parsing
  • Type Safety
  • Validation
  • Streaming
  • OpenAI
  • Anthropic

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/mlops/instructor'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
---name: instructordescription: Structured LLM outputs validated with Pydantic.version: 1.0.0author: Orchestra Researchlicense: MITdependencies: [instructor, pydantic, openai, anthropic]platforms: [linux, macos, windows]metadata:  hermes:    tags: [Prompt Engineering, Instructor, Structured Output, Pydantic, Data Extraction, JSON Parsing, Type Safety, Validation, Streaming, OpenAI, Anthropic] --- # Instructor: Structured LLM Outputs ## 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 ```bash# Base installationpip install instructor # With specific providerspip install "instructor[anthropic]"  # Anthropic Claudepip install "instructor[openai]"     # OpenAIpip install "instructor[all]"        # All providers``` ## Quick Start ### Basic Example: Extract User Data ```pythonimport 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 ```pythonfrom 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 ```pythonfrom 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 ```pythonclass 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 ```pythonfrom 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 description``` #### Enums for Constraints ```pythonfrom 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.POSITIVE``` ### 2. Validation Pydantic validates LLM outputs automatically. If validation fails, Instructor retries. #### Built-in Validators ```pythonfrom 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 automatically``` #### Custom Validators ```pythonfrom 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 v``` #### Model-Level Validation ```pythonfrom 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 self``` ### 3. Automatic Retrying Instructor retries automatically when validation fails, providing error feedback to the LLM. ```python# 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 extraction``` **How it works:**1. LLM generates output2. Pydantic validates3. If invalid: Error message sent back to LLM4. LLM tries again with error feedback5. Repeats up to max_retries ### 4. Streaming Stream partial results for real-time processing. #### Streaming Partial Objects ```pythonfrom 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-time``` #### Streaming Iterables ```pythonclass 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 arrives``` ## Provider Configuration ### Anthropic Claude ```pythonimport 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 ```pythonfrom 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) ```pythonfrom 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 ### Pattern 1: Data Extraction from Text ```pythonclass 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 ```pythonclass 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 ```pythonclass 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 ```pythonclass 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 ```pythondef 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 ### Union Types ```pythonfrom 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 content``` ### Dynamic Models ```pythonfrom 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 ```python# 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 ```python# 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 ```pythonfrom 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 ```pythonclass 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 ### 1. Clear Field Descriptions ```python# ❌ 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 ```python# ✅ 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 ```pythonmessages = [{    "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 ```python# ✅ Good: Enum ensures valid valuesclass Status(str, Enum):    PENDING = "pending"    APPROVED = "approved"    REJECTED = "rejected" class Application(BaseModel):    status: Status  # LLM must choose from enum``` ### 5. Handle Missing Data Gracefully ```pythonclass PartialData(BaseModel):    required_field: str    optional_field: Optional[str] = None    default_field: str = "default_value" # LLM only needs to provide required_field``` ## 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 - **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 - `references/validation.md` - Advanced validation patterns- `references/providers.md` - Provider-specific configuration- `references/examples.md` - Real-world use cases   
Discovery context

Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.