instructor

Get reliable, validated structured JSON from any LLM provider using Pydantic models and automatic retries.

Library
PyPI
v1.17.0
13,994 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
89 /100 Excellent
Development Activity 96
Maintenance 100
Community 68
Maturity 52
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
89 /100 Excellent
Architecture 85
Code Quality 92
Innovation 85
Learning Curve 95

Instructor turns raw LLM chat completions into typed, validated Python objects. You define the shape of the data you want as a Pydantic BaseModel, pass it as response_model to a patched client, and get back an instance of that model instead of a JSON string you have to parse and hope is well-formed. Under the hood it patches the target SDK’s completion method, generates a JSON-schema-based function/tool call from the Pydantic model, and validates the response against that schema.

What sets it apart from writing this glue code yourself is the retry loop: when the LLM’s output fails Pydantic validation, Instructor catches the ValidationError, feeds the error back into the conversation as additional context, and asks the model again, up to a configurable number of attempts, via tenacity-backed retry policies. A single instructor.from_provider("openai/gpt-4o-mini") style entry point abstracts over more than a dozen backends (OpenAI, Anthropic, Gemini, Cohere, Mistral, Bedrock, Vertex AI, Groq, Cerebras, Fireworks, Perplexity, Writer, xAI, LiteLLM, and more), so switching providers is a one-line change rather than a rewrite. Streaming support (Partial, IterableModel) lets you consume partially-built or list-of-object results as the model generates them, and multimodal helpers (Image, Audio) normalize vision/audio inputs across providers that support them.

What You Get

  • A single from_provider() factory that patches over a dozen LLM SDKs (OpenAI, Anthropic, Gemini, Cohere, Mistral, Bedrock, Vertex AI, Groq, and more) behind one consistent create(response_model=...) interface
  • Automatic JSON-schema generation from Pydantic BaseModel classes, with the schema passed to the provider as a tool/function call definition
  • Validation-driven reasking: failed Pydantic validation is fed back to the LLM as conversation context and retried via configurable tenacity retry policies
  • Streaming DSL types (Partial, IterableModel) for consuming partially-built objects or lists of objects as the model generates them
  • Multimodal input helpers (Image, Audio) that normalize vision/audio payloads across supporting providers
  • A bundled instructor CLI for creating, tracking, downloading, and cancelling OpenAI/Anthropic batch jobs

Common Use Cases

  • Extracting structured line-item or entity data from unstructured text (invoices, support tickets, emails) into typed models instead of regex or manual JSON repair
  • Producing strict, schema-validated tool-call arguments for an LLM agent so downstream code can call the tool without extra validation
  • Switching LLM providers with a one-line change to from_provider() while keeping the same response models and retry logic
  • Streaming progressively-complete structured JSON to a frontend as the model still generates, using Partial[Model]

Under The Hood

Architecture Instructor’s real implementation lives under instructor/v2/, with a thin instructor/core/* compatibility layer that re-exports from it. The central mechanism is patch_v2 (instructor/v2/core/patch.py), which wraps a provider client’s completion method and dispatches to a per-(provider, mode) handler via a hierarchical mode_registry (instructor/v2/core/registry.py). Each provider gets its own instructor/v2/providers/<name>/client.py exposing a from_<provider>() factory (e.g. from_anthropic, from_bedrock, from_gemini), all funneled through auto_client.py’s string-based from_provider("openai/gpt-4o-mini") dispatcher. Response-model handling (function_calls.py, response_model.py, schema.py) generates provider-specific JSON schemas from Pydantic models, retry.py drives the validation-failure reask loop, and hooks.py exposes lifecycle events for observability — a modular, provider-plugin-style layout that isolates the ~15 supported SDKs behind a shared core.

Tech Stack Python 3.9+, built with hatchling and managed via uv (a checked-in uv.lock). Core runtime dependencies are pydantic 2.8+ for schema generation and validation, tenacity for retry orchestration, jinja2 for prompt templating, docstring-parser for pulling field descriptions out of docstrings, and typer/rich for the bundled CLI. It has no web-framework dependency of its own — it’s a client-side library that patches whichever provider SDK (openai, anthropic, google-genai, etc.) the caller already has installed. Releases are published to PyPI via an automated python-publish.yml GitHub Actions workflow.

Code Quality The project has an extensive test suite — over 200 test files across tests/, split into unit, integration, and per-provider coverage suites, run with pytest-xdist for parallelism and both --asyncio-mode=auto sync/async coverage. CI additionally runs ruff for linting and formatting, the ty type checker in --error-on-warning mode against both source and tests, a dedicated doc-tests workflow that executes the code samples embedded in the documentation, and even a mutation-testing workflow — a level of rigor well beyond typical open-source libraries. Errors are modeled explicitly through a dedicated v2/core/errors.py and exceptions.py rather than being allowed to bubble up as raw provider exceptions.

API Design The defining ergonomic choice is that using the library requires almost no boilerplate: define a BaseModel, call client.chat.completions.create(response_model=Model, messages=[...]), and get a validated instance back — no manual JSON parsing, no hand-written retry loop. The validation-driven reask pattern (feeding a Pydantic ValidationError back to the LLM as context) is the library’s signature idea, and the single from_provider() string-based entry point keeps switching between more than a dozen LLM backends a one-line change rather than a rewrite of calling code.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers