Pydantic and Zod for LLM Output Validation
Why You Need Both Layers
Constrained decoding guarantees structural validity: the response has the right fields with the right types. But structural validity is not the same as correctness. A schema that requires a "price" field of type number will always get a number, but that number might be -5.00, 0.00, or 999999.99 when the actual price in the document is $29.95. A schema that requires an "email" field of type string will always get a string, but that string might be "the user's email was not provided" when you expected an email address or null.
These are not constrained decoding failures; the responses conform to the schema perfectly. They are semantic errors that the schema cannot express. JSON Schema has no concept of "a positive number" or "a valid email address" or "a string shorter than 500 characters" in the context of LLM structured output, because most providers do not support format, minimum, maximum, or minLength/maxLength keywords in their constrained decoding engines.
This is where validation libraries fill the gap. You define your schema as a Pydantic model or Zod schema, generate the JSON schema from it for the LLM provider, receive the structurally valid response, and then parse it through the validation library to catch semantic errors. The validation library is the second checkpoint, catching everything the schema engine cannot.
Pydantic for Python
Pydantic is the dominant data validation library in the Python ecosystem, with over 300 million monthly downloads. It defines data models as Python classes with type annotations, generates JSON schemas from those classes, and validates data against both structural and semantic rules.
A Pydantic model for a product extraction task might define a "name" field as a string with a minimum length of 1, a "price" field as a float with a constraint that it must be greater than 0, a "currency" field as a Literal type with allowed values "USD", "EUR", and "GBP", and an "in_stock" field as a boolean. This single class definition serves three purposes: it documents the data structure, it generates the JSON schema for the LLM provider, and it validates the LLM's response.
Field validators let you add custom validation logic to individual fields. A validator on the "email" field can check that the string matches an email regex pattern. A validator on the "date" field can check that the date is within a reasonable range. A validator on the "summary" field can check that it does not exceed a word count. These validators run after the JSON is parsed, so they catch semantic issues that the schema engine cannot.
Model validators let you validate relationships between fields. A model validator can check that "end_date" is after "start_date", that "total" equals the sum of "subtotal" and "tax", or that "status" is "shipped" only if "tracking_number" is not null. These cross-field validations are impossible to express in JSON Schema but are common in business logic.
JSON Schema generation is built into Pydantic. Calling MyModel.model_json_schema() produces a JSON Schema that you can pass directly to the LLM provider. The generated schema includes type information, enum constraints from Literal types, and descriptions from Field objects. This means you define your data model once and get both the schema for the LLM and the validation for the response from the same source of truth.
The practical workflow is: define a Pydantic model, extract its JSON schema with model_json_schema(), pass that schema to the LLM provider's structured output parameter, receive the response, and parse it with MyModel.model_validate_json(response_text). If validation passes, you have a fully typed, validated Python object. If validation fails, you get a detailed error message explaining exactly which field failed and why, which you can feed back to the model for a retry (this is what the Instructor library automates, covered on the Instructor page).
Zod for TypeScript
Zod is the TypeScript equivalent of Pydantic: a schema declaration and validation library that provides type inference, runtime validation, and JSON Schema generation. It is the standard choice for TypeScript LLM applications.
A Zod schema uses a builder pattern: z.object({name: z.string().min(1), price: z.number().positive(), currency: z.enum(["USD", "EUR", "GBP"]), in_stock: z.boolean()}). This defines the same product extraction schema as the Pydantic example, with the same structural and semantic constraints. Zod infers TypeScript types from the schema, so you get compile-time type checking in addition to runtime validation.
Refinements in Zod are the equivalent of Pydantic's field validators. You can chain .refine() calls to add custom validation logic: z.string().refine(val => val.includes("@"), {message: "Must be a valid email"}). Refinements run after parsing and can perform any validation logic you need.
Transforms let you modify the data during validation. A transform can convert a string date to a Date object, normalize capitalization, or compute derived fields. This is useful when the LLM's output is structurally correct but needs minor transformations before your application can use it.
JSON Schema generation is available through the zod-to-json-schema library, which converts Zod schemas to JSON Schema for the LLM provider. OpenAI's Node SDK also has built-in support for Zod schemas through the zodResponseFormat helper, which handles the conversion automatically.
The workflow mirrors Pydantic: define a Zod schema, convert it to JSON Schema, pass it to the LLM, parse the response with schema.parse(JSON.parse(response)), and get a fully typed, validated object or a detailed error. The Instructor library also supports Zod in its TypeScript version, providing automatic retry on validation failure.
Pydantic vs Zod: Which to Choose
The choice is determined by your programming language. If you are writing Python, use Pydantic. If you are writing TypeScript, use Zod. Both libraries have equivalent capabilities for LLM output validation:
Both generate JSON Schema from their native schema definitions. Both provide structural and semantic validation. Both support custom validators for complex business rules. Both integrate with the major LLM provider SDKs. Both work with the Instructor library for automatic retry on validation failure.
If you are building a system with both Python and TypeScript components, use the library native to each component and share the JSON Schema between them as the common contract. The LLM provider does not care whether the schema was generated by Pydantic or Zod; it receives a JSON Schema and enforces it identically.
Common Validation Patterns
Range validation: Constrain numeric fields to reasonable ranges. A sentiment score should be between 0 and 1. A word count should be positive. A year should be between 1900 and 2100. These ranges prevent the model from returning technically valid but practically absurd values.
String format validation: Check that strings match expected patterns. Emails should contain an @ sign. URLs should start with http or https. Phone numbers should match a digit pattern. Dates should be parseable. These checks catch cases where the model fills a string field with descriptive text instead of the expected formatted value.
Cross-field consistency: Verify that related fields are consistent. If "has_discount" is true, "discount_percent" should be greater than 0. If "status" is "completed", "completion_date" should not be null. These validations encode business rules that the model might not know about.
Length constraints: Limit string lengths for fields that feed into systems with size limits. A database column that accepts 255 characters should not receive a 1000-character summary. A tweet field should not exceed 280 characters. These constraints prevent downstream failures.
Null handling: Define explicit behavior for null values. A nullable field should be null only when the information is genuinely absent from the input, not when the model is uncertain. You can add validators that check whether a field is null and, if so, verify that a companion "reason" field explains why.
Pydantic and Zod provide the semantic validation layer that complements the structural guarantees of constrained decoding. Define your schema in these libraries, generate JSON Schema from them for the LLM provider, and validate the response through them for business logic checks. This two-layer approach, structural enforcement at the provider level and semantic validation at the application level, is the production standard for reliable LLM output.