instructor

安装量: 183
排名: #4675

安装

npx skills add https://github.com/davila7/claude-code-templates --skill instructor

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

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 structure

class User(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) # 30 print(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

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 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 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 description

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.POSITIVE

  1. Validation

Pydantic validates LLM outputs automatically. If validation fails, Instructor retries.

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 automatically

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 v

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 self
  1. 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

class Story(BaseModel): title: str content: str tags: list[str]

Stream partial updates as LLM generates

for 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 class Task(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 )

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) 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 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 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 )

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 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 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 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 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 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 client

with 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 gracefully

except Exception as e: print(f"API error: {e}")

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 1. Clear Field Descriptions

❌ Bad: Vague

class Product(BaseModel): name: str price: float

✅ Good: Descriptive

class Product(BaseModel): name: str = Field(description="Product name from the text") price: float = Field(description="Price in USD, without currency symbol")

  1. Use Appropriate Validation

✅ Good: Constrain values

class 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")

  1. Provide Examples in Prompts messages = [{ "role": "user", "content": """Extract person info from: "John, 30, engineer"

Example format: { "name": "John Doe", "age": 30, "occupation": "engineer" }""" }]

  1. Use Enums for Fixed Categories

✅ Good: Enum ensures valid values

class Status(str, Enum): PENDING = "pending" APPROVED = "approved" REJECTED = "rejected"

class Application(BaseModel): status: Status # LLM must choose from enum

  1. 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_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

返回排行榜