DEV Community

Srujana Maddula
Srujana Maddula

Posted on

A Beginner’s guide to Instructor: Get Structured Outputs From LLMs

LLMs generate text by predicting the next best token. There's no guarantee the output looks the way you expect. And often you don't need the full response, you just need specific parts of it to feed into an API, store in a database, or pass to the next layer of your application.

You might think about writing a JSON parser for this. The problem is the model returns unpredictable text. It might skip fields, add extra text, or change the structure. You can't build a parser for output you can’t predict.

By the end of this tutorial, you'll know how to use the Instructor library to handle this. I'll also walk through a real-world use case so you can adapt it to your own projects.

What is Instructor and How Does it Work?

Instructor is an open-source Python library for getting structured output from LLMs. It supports many providers (OpenAI, Anthropic, Gemini, Ollama, DeepSeek, etc.) and guides them to return responses in a format you define.

Here's the basic flow:

  1. You define your output format as a Pydantic model.
  2. Instructor derives a schema from that model and includes it in the LLM request.
  3. The model generates a response guided by that schema.
  4. Instructor validates the response and maps it to your Pydantic model.
  5. If validation fails, it retries with the error fed back so the model can correct itself.

Install and Setup Instructor

Install Instructor pip:

pip install instructor
Enter fullscreen mode Exit fullscreen mode

This installs Instructor with the OpenAI client by default. If you're using a different provider, install the relevant extra:

pip install "instructor[anthropic]"    # Anthropic
pip install "instructor[google-genai]"    # Google/Gemini
Enter fullscreen mode Exit fullscreen mode

Also, make sure you have these installed before you start:

  • Python 3.9+
  • Your model provider's client (e.g. openai, anthropic)
  • Python-dotenv (recommended to manage API keys)

Get Structured Outputs with Instructor and Pydantic Models

Say you're building a customer support ticket routing agent. A user submits a query and you want to extract specific fields from it: the order ID, issue type, priority, and requested action, then pass them to your routing logic. That's the use case we'll build here.

1. Load your API key

To call the OpenAI API, you need an API key. Get it from the OpenAI platform and store it in a .env file.

OPENAI_API_KEY=your_api_key_here

If you're using a different provider, store their API key here instead.

Then load it in your Python script:

from dotenv import load_dotenv  
load_dotenv()
Enter fullscreen mode Exit fullscreen mode

2. Define a Pydantic Model

Instructor needs to know your expected output structure. You define that using a Pydantic model, a Python class with typed fields.

from pydantic import BaseModel  
from typing import Literal, Optional

class SupportTicket(BaseModel):
   order_id: Optional[str] = None
   issue_type: Literal["delivery_issue", "technical_issue", "billing_issue", "product_issue", "others"]
   priority: Literal["high", "low", "medium"]
   requested_action: Literal["refund", "support", "replacement", "status_check", "others"]
Enter fullscreen mode Exit fullscreen mode

In this code, SupportTicket inherits from Pydantic's BaseModel. Literal restricts issue_type, priority, and requested_action fields to a fixed set of values, so if the model returns something outside that list, validation fails. Optional on order_id means it can be a string or None since not every ticket will have order ID information.

Pydantic converts these type hints into validation rules at runtime. If the LLM returns a field with the wrong type, Pydantic either coerces it to the right type or flags it as invalid, which triggers Instructor's retry mechanism.

3. Call the Model

To call the model, first patch the OpenAI client with Instructor:

client = instructor.from_openai(OpenAI())
Enter fullscreen mode Exit fullscreen mode

Then call client.chat.completions.create() the same way you normally would, with one addition: response_model=SupportTicket.

result = client.chat.completions.create(
   model="gpt-4o-mini",
   response_model=SupportTicket,
   messages=[
       {
           "role": "user",
           "content": "It says my account is disabled. Can you unlock it?"
       }
   ]
)

print(result)
Enter fullscreen mode Exit fullscreen mode

The model generates a response, Instructor parses it into the SupportTicket schema and returns a validated SupportTicket instance. If parsing or validation fails, it feeds the error back to the model and retries.

Here's the full code together:

import instructor
from pydantic import BaseModel
from openai import OpenAI
from typing import Literal, Optional
from dotenv import load_dotenv

load_dotenv()


class SupportTicket(BaseModel):
    order_id: Optional[str] = None
    issue_type: Literal["delivery_issue", "technical_issue", "billing_issue", "product_issue", "others"]
    priority: Literal["high", "low", "medium"]
    requested_action: Literal["refund", "support", "replacement", "status_check", "others"]


client = instructor.from_openai(OpenAI())

result = client.chat.completions.create(
    model="gpt-4o-mini",
    response_model=SupportTicket,
    messages=[
        {
            "role": "user",
            "content": "It says my account is disabled. Can you unlock it?",
        }
    ],
)

print(result)
Enter fullscreen mode Exit fullscreen mode

Output:

LLM structured output

The model classified the query as a technical issue with high priority and flagged the action as support. That's a usable object your routing logic can act on directly.

What to Explore Next

This tutorial covered the basics, but there's more to Instructor. A few things worth adding to your implementation:

  • Error handling: API calls can fail for a number of reasons: network issues, invalid API keys, or hitting the token limit. Wrap your calls in try/except blocks and handle each failure case with a clear error message so your app doesn't silently break.
  • Retry limits: Set the max_retries parameter to control how many times Instructor retries before giving up. Pick a reasonable number that gives the model enough attempts to handle vague queries without burning through tokens.
  • System prompts: A system prompt is how you tell the model who it is and what it's supposed to do. Without one, the model just sees the user message and the schema with no context. For simple queries that's fine, but for vague inputs like "I need help" the model starts guessing. Adding something like "You are a support ticket classifier. Extract structured information from the user's message." tells the model exactly how to interpret the input and keeps classification consistent across different queries.
  • Nested models: Once you're comfortable with flat models, try nesting Pydantic models inside each other. For example, a SupportTicket model can include a nested CustomerDetails model and a list of IssueMetadata entries.

For more advanced topics, check out Pydantic AI to build type-safe agents. If you're focused on OpenAI specifically, learn how to integrate the ChatGPT API into your Python projects and work with structured data.

Top comments (0)