$npx -y skills add AlexAI-MCP/hermes-CCC --skill instructorStructured LLM outputs with Instructor — Pydantic models as response schemas for OpenAI, Anthropic, and any OpenAI-compatible API.
| 1 | # Instructor — Structured LLM Outputs |
| 2 | |
| 3 | Get type-safe, validated Pydantic objects from any LLM instead of raw strings. |
| 4 | |
| 5 | ## Setup |
| 6 | |
| 7 | ```bash |
| 8 | pip install instructor pydantic |
| 9 | pip install anthropic # or openai |
| 10 | ``` |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Basic Usage (Anthropic) |
| 15 | |
| 16 | ```python |
| 17 | import anthropic |
| 18 | import instructor |
| 19 | from pydantic import BaseModel |
| 20 | |
| 21 | client = instructor.from_anthropic(anthropic.Anthropic()) |
| 22 | |
| 23 | class UserProfile(BaseModel): |
| 24 | name: str |
| 25 | age: int |
| 26 | skills: list[str] |
| 27 | experience_years: int |
| 28 | |
| 29 | profile = client.messages.create( |
| 30 | model="claude-sonnet-4-6", |
| 31 | max_tokens=1024, |
| 32 | messages=[{ |
| 33 | "role": "user", |
| 34 | "content": "Extract: John is a 32-year-old Python developer with 8 years experience in ML and DevOps." |
| 35 | }], |
| 36 | response_model=UserProfile, |
| 37 | ) |
| 38 | |
| 39 | print(profile.name) # "John" |
| 40 | print(profile.age) # 32 |
| 41 | print(profile.skills) # ["Python", "ML", "DevOps"] |
| 42 | print(profile.experience_years) # 8 |
| 43 | ``` |
| 44 | |
| 45 | --- |
| 46 | |
| 47 | ## With OpenAI |
| 48 | |
| 49 | ```python |
| 50 | import openai |
| 51 | import instructor |
| 52 | |
| 53 | client = instructor.from_openai(openai.OpenAI()) |
| 54 | |
| 55 | result = client.chat.completions.create( |
| 56 | model="gpt-4o", |
| 57 | messages=[{"role": "user", "content": "..."}], |
| 58 | response_model=UserProfile, |
| 59 | ) |
| 60 | ``` |
| 61 | |
| 62 | --- |
| 63 | |
| 64 | ## Nested Models |
| 65 | |
| 66 | ```python |
| 67 | from pydantic import BaseModel, Field |
| 68 | from typing import Optional |
| 69 | |
| 70 | class Address(BaseModel): |
| 71 | street: str |
| 72 | city: str |
| 73 | country: str |
| 74 | |
| 75 | class Company(BaseModel): |
| 76 | name: str |
| 77 | industry: str |
| 78 | founded_year: int |
| 79 | headquarters: Address |
| 80 | employee_count: Optional[int] = None |
| 81 | |
| 82 | class ResearchPaper(BaseModel): |
| 83 | title: str |
| 84 | authors: list[str] |
| 85 | abstract: str |
| 86 | key_findings: list[str] = Field(description="3-5 bullet points") |
| 87 | methodology: str |
| 88 | year: int |
| 89 | ``` |
| 90 | |
| 91 | --- |
| 92 | |
| 93 | ## Validation with Pydantic |
| 94 | |
| 95 | ```python |
| 96 | from pydantic import BaseModel, field_validator, Field |
| 97 | |
| 98 | class SentimentAnalysis(BaseModel): |
| 99 | sentiment: str = Field(description="positive, negative, or neutral") |
| 100 | confidence: float = Field(ge=0, le=1) |
| 101 | reasoning: str |
| 102 | |
| 103 | @field_validator("sentiment") |
| 104 | def validate_sentiment(cls, v): |
| 105 | if v not in ["positive", "negative", "neutral"]: |
| 106 | raise ValueError("Must be positive, negative, or neutral") |
| 107 | return v |
| 108 | ``` |
| 109 | |
| 110 | --- |
| 111 | |
| 112 | ## Streaming Partial Objects |
| 113 | |
| 114 | ```python |
| 115 | from instructor import Partial |
| 116 | |
| 117 | for partial_profile in client.messages.stream( |
| 118 | model="claude-sonnet-4-6", |
| 119 | max_tokens=1024, |
| 120 | messages=[{"role": "user", "content": "..."}], |
| 121 | response_model=Partial[UserProfile], |
| 122 | ): |
| 123 | print(partial_profile) # updates as tokens arrive |
| 124 | ``` |
| 125 | |
| 126 | --- |
| 127 | |
| 128 | ## Batch Extraction |
| 129 | |
| 130 | ```python |
| 131 | from typing import Iterable |
| 132 | |
| 133 | class Contact(BaseModel): |
| 134 | name: str |
| 135 | email: str |
| 136 | phone: Optional[str] |
| 137 | |
| 138 | # Extract multiple contacts from one text |
| 139 | class ContactList(BaseModel): |
| 140 | contacts: list[Contact] |
| 141 | |
| 142 | text = """ |
| 143 | Alice: alice@example.com, 555-1234 |
| 144 | Bob: bob@example.com |
| 145 | Carol: carol@example.com, 555-5678 |
| 146 | """ |
| 147 | |
| 148 | result = client.messages.create( |
| 149 | model="claude-haiku-4-5", |
| 150 | max_tokens=512, |
| 151 | messages=[{"role": "user", "content": f"Extract contacts:\n{text}"}], |
| 152 | response_model=ContactList, |
| 153 | ) |
| 154 | |
| 155 | for contact in result.contacts: |
| 156 | print(contact.name, contact.email) |
| 157 | ``` |
| 158 | |
| 159 | --- |
| 160 | |
| 161 | ## With vLLM / Local Models |
| 162 | |
| 163 | ```python |
| 164 | client = instructor.from_openai( |
| 165 | openai.OpenAI( |
| 166 | base_url="http://localhost:8000/v1", |
| 167 | api_key="not-needed" |
| 168 | ), |
| 169 | mode=instructor.Mode.JSON, |
| 170 | ) |
| 171 | ``` |
| 172 | |
| 173 | --- |
| 174 | |
| 175 | ## Use Cases |
| 176 | |
| 177 | - Entity extraction from documents |
| 178 | - Structured data from unstructured text |
| 179 | - Classification with confidence scores |
| 180 | - RAG with typed outputs |
| 181 | - Form filling automation |
| 182 | - API response parsing |