← Back to DevBytes

Instructor: Structured Extraction from LLMs in Python

Introduction to Instructor: Structured Extraction from LLMs

Large Language Models (LLMs) are incredibly powerful at understanding and generating text, but when building applications, developers rarely need raw text. They need structured data—like JSON objects—that can be easily integrated into databases, APIs, and application logic. Traditionally, getting an LLM to output reliable JSON involved complex prompt engineering, fragile parsing, and manual error handling.

Instructor is a Python library that solves this problem by bridging the gap between LLMs and structured data. It leverages Pydantic to define the exact shape of the data you want, and it patches your LLM client to ensure the model returns data that strictly adheres to that schema.

Why Instructor Matters

Using Instructor provides several critical benefits for developers:

Getting Started with Instructor

To begin using Instructor, you need to install the library along with your preferred LLM provider's SDK and Pydantic. In this tutorial, we will use OpenAI as the primary example.

pip install instructor openai pydantic

Defining Data Models with Pydantic

The core of Instructor is the Pydantic model. You define a class that inherits from BaseModel and declare the fields you want the LLM to extract. You can also use Pydantic's Field to provide descriptions, which act as additional instructions for the LLM.

from pydantic import BaseModel, Field
from typing import List

class UserDetail(BaseModel):
    """Extracted user information."""
    name: str = Field(..., description="The full name of the user")
    age: int = Field(..., description="The age of the user in years")
    interests: List[str] = Field(default_factory=list, description="A list of the user's hobbies or interests")

Your First Structured Extraction

To use the model, you first patch the OpenAI client using instructor.from_openai(). Then, you pass your Pydantic model to the response_model parameter in the create method.

import instructor
from openai import OpenAI
from pydantic import BaseModel, Field

# 1. Define the model
class UserDetail(BaseModel):
    name: str = Field(..., description="The full name of the user")
    age: int = Field(..., description="The age of the user in years")

# 2. Patch the client
client = instructor.from_openai(OpenAI())

# 3. Extract the data
text_block = "John Doe is a 34-year-old software engineer who loves hiking."

user = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=UserDetail,
    messages=[
        {"role": "user", "content": text_block}
    ]
)

print(type(user))  # <class '__main__.UserDetail'>
print(user.name)   # John Doe
print(user.age)    # 34

Notice that we did not have to ask the LLM to output JSON, nor did we have to parse a JSON string. Instructor handles all of this under the hood and returns a fully instantiated UserDetail object.

Advanced Techniques

Handling Lists and Nested Objects

Real-world data is rarely flat. Instructor handles complex, nested structures effortlessly. You can define models that contain other models, or models that return lists of objects.

from pydantic import BaseModel, Field
from typing import List

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Company(BaseModel):
    name: str
    industry: str
    headquarters: Address
    employees: int

# Extracting a list of companies from a text
class CompanyList(BaseModel):
    companies: List[Company]

# Usage:
# client.chat.completions.create(
#     model="gpt-4o-mini",
#     response_model=CompanyList,
#     messages=[{"role": "user", "content": "Extract companies from this text..."}]
# )

Validation and Retries

One of Instructor's most powerful features is its retry mechanism. If the LLM returns data that fails Pydantic validation, Instructor intercepts the error, appends the validation error message to the conversation, and prompts the LLM to try again.

You can control this behavior using the max_retries parameter. You can also add custom validation logic using Pydantic's field_validator or model_validator.

from pydantic import BaseModel, field_validator

class StrictUser(BaseModel):
    name: str
    age: int

    @field_validator("age")
    def check_age(cls, v):
        if v < 0 or v > 120:
            raise ValueError("Age must be between 0 and 120")
        return v

# If the LLM returns age=-5, Instructor will tell the LLM:
# "Validation failed: Age must be between 0 and 120. Please fix the output."
# and retry up to max_retries times.

user = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=StrictUser,
    max_retries=3,
    messages=[{"role": "user", "content": "Extract: Jane is -5 years old."}]
)

Best Practices for Production

To get the most out of Instructor in a production environment, consider the following best practices:

Conclusion

Instructor fundamentally changes how developers interact with Large Language Models. By treating LLMs as data extraction engines that return typed Python objects rather than unpredictable text generators, it brings reliability and developer experience to AI applications. By combining the robust validation of Pydantic with intelligent retry mechanisms, Instructor allows you to build production-ready pipelines that you can actually trust. Whether you are building a simple data scraper or a complex autonomous agent, Instructor is an essential tool for bridging the gap between unstructured language and structured code.

— Ad —

Google AdSense will appear here after approval

← Back to all articles