Model or dataset
567-labs/instructor avatar
567-labs/instructor

Instructor: Pydantic-Validated JSON Extraction from LLM Responses

structured outputs for llms

13,897 stars1,245 forksPythonMIT

At a glance

What is it?
Instructor wraps provider clients so a Pydantic model replaces hand-written JSON schema parsing. It is a good fit for schema-first extraction, and the README itself points elsewhere once an application needs agent runs.
Who is it for?
Adopt Instructor for extraction-shaped work: pulling typed records out of text with a Pydantic model and retrying when validation fails. Do not adopt it if the application needs typed tools, replayable datasets, evals or agent-run observability, because the README directs that work to PydanticAI.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem is the gap between a JSON schema and a validated Python object

The README frames the work in five steps: write complex JSON schemas, handle validation errors, retry failed extractions, parse unstructured responses, and deal with different provider APIs. Instructor's claim is that one interface covers all five. The audience is Python developers who already think in Pydantic models and want the model to be the schema. The README's own contrast example shows the manual path: build a tools array with a function definition, read choices[0].message.tool_calls[0], json.loads the arguments string, then check whether a required key is present and branch into error handling. Instructor replaces that with response_model=User on a client created by instructor.from_provider. The library is not a prompting framework and not an agent runtime. It is a thin contract layer: you declare fields and types, the library gets the model to fill them, and Pydantic decides whether the result is acceptable.

from_provider builds a client whose create() accepts response_model

The mechanism visible in the README is a client factory plus a request parameter. instructor.from_provider takes a string of the form provider/model, for example openai/gpt-4o-mini, anthropic/claude-3-5-sonnet, google/gemini-pro, ollama/llama3.2, or groq/llama-3.1-8b-instant. The returned object exposes chat.completions.create, matching the OpenAI SDK call shape, and that call accepts response_model alongside messages. The return value is an instance of the model, not a dict and not a raw response, which is why the README can print User(name='John', age=25) directly. API keys can come from environment variables or be passed as api_key="sk-..." to from_provider. Nested models are handled without extra configuration: the README defines an Address model and a User model with addresses: List[Address], and states that Instructor handles nested objects automatically. Streaming uses a wrapper type rather than a new API: response_model=Partial[User] with stream=True yields successive instances where fields fill in over time, starting as User(name=None, age=None) and ending as User(name="John", age=25). That Partial behaviour is the one place where the object you receive is deliberately incomplete, so downstream code must tolerate None.

Validation failure is the retry trigger, not an exception you catch

The retry design is the part worth understanding before adopting. A field_validator on the Pydantic model raises ValueError when a value is unacceptable, for example an age below zero. The README states that Instructor automatically retries when validation fails and that the error message is sent back, with max_retries=3 controlling the attempt count. This means your validation logic is also your correction prompt: the text of the exception is what the model sees. Two consequences follow. First, a vague error message produces a vague correction, so validators should say what was wrong in terms a model can act on. Second, retries cost tokens and latency, and the README gives no guidance on a sensible ceiling, so max_retries is a budget decision you make per call site. If validation keeps failing after the retry budget is exhausted, the failure surfaces as an exception at the call site. Instructor does not silently return a partially filled object in that path; the None-filled objects appear only in the Partial streaming case.

Installation and the smallest working call

Installation is a single package: pip install instructor, or uv add instructor, or poetry add instructor. The README's minimal example imports instructor and BaseModel, defines a User model with name: str and age: int, creates the client with instructor.from_provider("openai/gpt-4o-mini"), and calls client.chat.completions.create(response_model=User, messages=[{"role": "user", "content": "John is 25 years old"}]). The documented output is User(name='John', age=25). To use a key without environment variables, pass api_key to from_provider, as in instructor.from_provider("anthropic/claude-3-5-sonnet", api_key="sk-ant-..."). For streaming, import Partial from instructor and set response_model=Partial[User] with stream=True, then iterate. For retries, add max_retries=3 to the same create call. Note that the README's model identifiers, such as gpt-4o-mini and gpt-5.4-mini, are illustrative strings passed to the provider; they are not validated by Instructor, so a typo surfaces as a provider error rather than a library error.

Where Instructor stops: agents, observability and the PydanticAI handoff

The README contains an unusual admission for a project page. It says to use Instructor for fast extraction and to reach for PydanticAI when you need agents, describing PydanticAI as the official agent runtime from the Pydantic team with typed tools, replayable datasets, evals and production dashboards, while using the same Pydantic models. That is a real boundary, not marketing modesty. If your application needs to call tools in a loop, record a run for later replay, or evaluate outputs across a dataset, Instructor's surface does not cover it and you would be building that layer yourself. A second limitation is structural: the library is Python-first, and the README lists separate implementations for TypeScript, Ruby, Go, Elixir and Rust rather than a single shared core. Feature parity across those ports is not something the README claims, so a polyglot team should check each port's own documentation before assuming the same behaviour, particularly around Partial streaming and retry semantics.

Licence, releases and the cost of staying current

The repository is MIT licensed, which permits commercial use and modification with the licence and copyright notice retained. That is permissive, and it means the practical cost of adoption is not legal review but version tracking. The release cadence visible in the supplied material is roughly one minor release every few weeks: v1.15.4 in late June 2026, v1.16.0 in late August, v1.17.0 in early September. A cadence that fast cuts both ways. Fixes for provider API drift arrive quickly, which matters because the library sits between your code and several external APIs. It also means pinning a version is the safer default, and that the README on the default branch may describe behaviour that your installed version does not have. The README's own examples already mix model names across generations, which suggests the documentation tracks the current release rather than any historical one. Treat the release notes for the version you pin as the authority, not the README. This is a description of the licence terms, not legal advice; if you redistribute Instructor inside a product, confirm the notice requirements with your own counsel.

The alternative is the same models with a different runtime

PydanticAI is the alternative the README itself names, and the difference is scope rather than syntax. Both consume the same Pydantic models, so a schema written for Instructor is not wasted if you migrate. What changes is what surrounds the model call. Instructor gives you a client, a response_model parameter, a retry loop driven by validation errors, and a Partial wrapper for streaming. PydanticAI adds typed tools, replayable datasets, evals and dashboards, which is the machinery you would otherwise assemble yourself around an extraction library. The trade is weight: an extraction script that reads a support ticket into a typed record does not need an agent runtime, and adding one introduces concepts the task never uses. The README's own framing is the clearest statement of the split available: simple schema-first flows stay in Instructor, richer agent runs go to PydanticAI. A team already using Pydantic v2 models for its domain layer can adopt either without rewriting the schemas.

Editorial conclusion

Adopt Instructor for extraction-shaped work: pulling typed records out of text with a Pydantic model and retrying when validation fails. Do not adopt it if the application needs typed tools, replayable datasets, evals or agent-run observability, because the README directs that work to PydanticAI. Before committing, verify the provider string for your target backend against the installed version, confirm whether you need from_provider or the older patch-style client construction, and check the installed package's release notes rather than assuming the README's examples match the version you pinned.

Official sources

  1. 567-labs/instructor on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes