instructor

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

Library
PyPI
v1.16.0
13,800stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
89/100Excellent
Development Activity96
Maintenance100
Community68
Maturity52
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
89/100Excellent
Architecture85
Code Quality92
Innovation85
Learning Curve95

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

Search