Instructor

orchestra-research/ai-research-skills/16-prompt-engineering/instructor

作者 orchestra-research773a52944ba4MIT13K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3个月前更新

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

仅含说明AI & Agents
AI 生成的概览

指导使用 Instructor 库,从大模型响应中获取经 Pydantic 校验的结构化输出。

功能
该技能说明如何使用 Instructor 这个 Python 库,借助 Pydantic 响应模型从大模型响应中提取结构化数据。内容涵盖定义字段、枚举、嵌套与可选类型的模型,添加内置与自定义校验器,以及在校验失败时配置自动重试。它还介绍流式输出部分对象与可迭代结果、Anthropic、OpenAI 及本地 Ollama 端点的接入方式,以及信息抽取、分类和批量处理等常见模式。
适用场景
适用于构建需要大模型返回带类型、经校验的结构化结果(而非纯文本或手工解析 JSON)的 Python 应用。适合信息抽取、分类和多实体抽取任务,以及需要在校验失败时自动重试或边生成边流式返回结果的场景。
运行要求
需要 Python 环境并安装 instructor、pydantic、openai 和 anthropic 包,同时需要所选服务商的 API 凭据或可访问的本地模型服务。该技能不包含脚本,仅为说明文档和参考文档。

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

python
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 [email protected]"    }],    response_model=User)
print(user.name)   # "John Doe"print(user.age)    # 30print(user.email)  # "[email protected]"

With OpenAI

python
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, [email protected]"}])

Core Concepts

1. Response Models (Pydantic)

Response models define the structure and validation rules for LLM outputs.

Basic Model
python
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
python
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
python
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
python
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

2. Validation

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

Built-in Validators
python
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
python
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
python
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

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 output
  2. Pydantic validates
  3. If invalid: Error message sent back to LLM
  4. LLM tries again with error feedback
  5. Repeats up to max_retries

4. Streaming

Stream partial results for real-time processing.

Streaming Partial Objects
python
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-time
Streaming Iterables
python
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 arrives

Provider Configuration

Anthropic Claude

python
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

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

python
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

Pattern 1: Data Extraction from Text

python
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

python
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

python
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

python
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

python
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

python
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

python
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

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

python
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

python
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": "[email protected]"                }            ]        }

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

python
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

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

python
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

FeatureInstructorManual JSONLangChainDSPy
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 CurveLowLowMediumHigh

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

See Also

  • references/validation.md - Advanced validation patterns
  • references/providers.md - Provider-specific configuration
  • references/examples.md - Real-world use cases

来源与署名

来源:orchestra-research/ai-research-skills位于16-prompt-engineering/instructor提交773a529

许可证: MIT

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架