CloudCodeTree LogoCloudCodeTree
AI NewsTutorialsAbout
CloudCodeTree Logo
CloudCodeTree
  • AI News
  • Tutorials
  • About
← Back to AI News
Get Typed Python Objects from Any LLM: Structured Output with instructor

Get Typed Python Objects from Any LLM: Structured Output with instructor

Chris Harper

3 min read

Jun 30, 2026 · 20:11 UTC

AI
Tutorial
Agents
LLM

TL;DR: Wrap any LLM client with instructor and a Pydantic model — the library validates the response schema, auto-retries on validation failure, and returns a typed Python object with zero JSON parsing.

What you'll be able to do after this:

  • Define exactly what data you need from an LLM as a Python dataclass — no prompt-engineering around output format
  • Get typed, validated Python objects back with no try/except JSON parsing loops
  • Build extraction pipelines that self-heal when the model returns a bad response

The problem instructor solves

Every agent and extraction pipeline hits the same wall: you ask Claude to return JSON and it responds with slightly-wrong JSON — a missing field, a string where you expected an int, or a stray comment. You add a try/except, then a retry, and soon the retry logic outweighs the feature.

instructor handles this at the library level. Define your output as a Pydantic BaseModel, and the library takes care of schema enforcement, retries, and parsing.

Install and wire up Claude

pip install instructor anthropic
import instructor
from anthropic import Anthropic
from pydantic import BaseModel

# Wrap the Anthropic client once at startup
client = instructor.from_anthropic(Anthropic())

Basic extraction

from typing import Literal

class BugReport(BaseModel):
    title: str
    severity: Literal["low", "medium", "high", "critical"]
    affected_files: list[str]
    suggested_fix: str

bug = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": f"Analyze this diff and extract a bug report:\n\n{diff}"
    }],
    response_model=BugReport,
)

# bug is a validated BugReport instance — no JSON.loads(), no KeyError
print(bug.severity)          # "critical"
print(bug.affected_files)    # ["src/auth.py", "src/middleware.py"]

Under the hood, instructor converts response_model into a tool definition, calls Claude with tool_use, parses the result into your Pydantic model, and retries automatically when validation fails. You never see the tool call plumbing.

Extracting a list of entities

class Dependency(BaseModel):
    name: str
    version: str
    is_dev: bool

class Dependencies(BaseModel):
    packages: list[Dependency]

result = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=2048,
    messages=[{
        "role": "user",
        "content": f"Extract all dependencies from this package.json:\n\n{package_json}"
    }],
    response_model=Dependencies,
)

for pkg in result.packages:
    print(f"{pkg.name}@{pkg.version}{'  [dev]' if pkg.is_dev else ''}")

Self-healing with Pydantic validators

When validation fails, instructor re-prompts the model with the error message and it self-corrects:

from pydantic import field_validator

class CodeReview(BaseModel):
    issues: list[str]
    overall_score: int

    @field_validator("overall_score")
    @classmethod
    def score_in_range(cls, v: int) -> int:
        if not 1 <= v <= 10:
            raise ValueError(f"Score must be 1–10, got {v}")
        return v

If Claude returns overall_score: 15, Pydantic raises a ValidationError, instructor re-prompts with the error, and the next response self-corrects. Default: 3 retries (set max_retries=n in from_anthropic()).

Same pattern, any provider

# Switch to a local Ollama model — same Pydantic schema, same code
import instructor
from openai import OpenAI

local = instructor.from_openai(
    OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
)
result = local.chat.completions.create(
    model="llama3.2",
    response_model=BugReport,
    messages=[{"role": "user", "content": prompt}],
)

instructor supports 15+ providers (Claude, GPT-4o, Gemini, Ollama, DeepSeek, OpenRouter, Fireworks) with the same response_model interface — swap the underlying model without touching business logic.

Sources: instructor docs | Anthropic integration | YouTube tutorial (Aug 2025) | GitHub