97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
385 lines
11 KiB
Markdown
385 lines
11 KiB
Markdown
# Field Validation
|
|
|
|
This guide covers how to add validation to fields when extracting structured data with Instructor. Field validation ensures that your extracted data meets specific criteria and constraints.
|
|
|
|
## Why Field Validation Matters
|
|
|
|
Field validation helps you:
|
|
|
|
1. Ensure data quality and consistency
|
|
2. Enforce business rules
|
|
3. Prevent errors in downstream processing
|
|
4. Provide clear feedback for invalid data
|
|
|
|
Instructor uses Pydantic's validation system, which is applied automatically during extraction.
|
|
|
|
## Basic Field Constraints
|
|
|
|
You can add basic constraints to fields using Pydantic's `Field` function:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
import instructor
|
|
from openai import OpenAI
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class User(BaseModel):
|
|
name: str = Field(..., min_length=2, max_length=50)
|
|
age: int = Field(..., ge=0, le=120) # greater than or equal to 0, less than or equal to 120
|
|
email: str = Field(..., pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
|
|
|
|
# Extract with validation
|
|
response = client.create(
|
|
model="gpt-5.4-mini",
|
|
messages=[
|
|
{"role": "user", "content": "I'm John Smith, 35 years old, with email john@example.com"}
|
|
],
|
|
response_model=User
|
|
)
|
|
```
|
|
|
|
Common Field constraints include:
|
|
|
|
| Constraint | Description | Example |
|
|
|------------|-------------|---------|
|
|
| `min_length` | Minimum string length | `min_length=2` |
|
|
| `max_length` | Maximum string length | `max_length=50` |
|
|
| `pattern` | Regex pattern to match | `pattern=r'^[0-9]+$'` |
|
|
| `gt` | Greater than | `gt=0` (for numbers) |
|
|
| `ge` | Greater than or equal | `ge=18` |
|
|
| `lt` | Less than | `lt=100` |
|
|
| `le` | Less than or equal | `le=120` |
|
|
| `min_items` | Minimum list items | `min_items=1` |
|
|
| `max_items` | Maximum list items | `max_items=10` |
|
|
|
|
For more information on field definitions, see the [Fields](../../concepts/fields.md) concepts page.
|
|
|
|
## Validation with Field Validators
|
|
|
|
For more complex validation logic, use Pydantic's `field_validator` decorator:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field, field_validator
|
|
import instructor
|
|
from openai import OpenAI
|
|
import re
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class Product(BaseModel):
|
|
name: str
|
|
sku: str
|
|
price: float
|
|
|
|
@field_validator('name')
|
|
@classmethod
|
|
def validate_name(cls, v):
|
|
if len(v.strip()) < 3:
|
|
raise ValueError("Product name must be at least 3 characters")
|
|
return v.strip()
|
|
|
|
@field_validator('sku')
|
|
@classmethod
|
|
def validate_sku(cls, v):
|
|
if not re.match(r'^[A-Z]{3}-\d{4}$', v):
|
|
raise ValueError("SKU must be in format XXX-0000")
|
|
return v
|
|
|
|
@field_validator('price')
|
|
@classmethod
|
|
def validate_price(cls, v):
|
|
if v <= 0:
|
|
raise ValueError("Price must be greater than zero")
|
|
if v > 10000:
|
|
raise ValueError("Price exceeds maximum allowed value")
|
|
return v
|
|
|
|
# Extract validated data
|
|
response = client.create(
|
|
model="gpt-5.4-mini",
|
|
messages=[
|
|
{"role": "user", "content": "Product: Wireless Headphones, SKU: ABC-1234, Price: $79.99"}
|
|
],
|
|
response_model=Product
|
|
)
|
|
```
|
|
|
|
Field validators can:
|
|
- Perform complex validation logic
|
|
- Clean and normalize data
|
|
- Transform values
|
|
- Check values against external data sources
|
|
|
|
For more on custom validators, see the [Custom Validators](../validation/custom_validators.md) guide.
|
|
|
|
## Model-level Validation
|
|
|
|
Sometimes validation needs to check relationships between fields. For this, use `model_validator`:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field, model_validator
|
|
import instructor
|
|
from openai import OpenAI
|
|
from datetime import date
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class DateRange(BaseModel):
|
|
start_date: date
|
|
end_date: date
|
|
|
|
@model_validator(mode='after')
|
|
def validate_date_range(self):
|
|
if self.end_date < self.start_date:
|
|
raise ValueError("End date must be after start date")
|
|
return self
|
|
```
|
|
|
|
## Validation in Nested Structures
|
|
|
|
You can apply validation at any level in nested structures:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field, field_validator
|
|
import instructor
|
|
from openai import OpenAI
|
|
from typing import List
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class Address(BaseModel):
|
|
street: str
|
|
city: str
|
|
state: str
|
|
zip_code: str
|
|
|
|
@field_validator('state')
|
|
@classmethod
|
|
def validate_state(cls, v):
|
|
valid_states = {"CA", "NY", "TX", "FL"} # Example: just a few states
|
|
if v not in valid_states:
|
|
raise ValueError(f"State must be one of: {', '.join(valid_states)}")
|
|
return v
|
|
|
|
@field_validator('zip_code')
|
|
@classmethod
|
|
def validate_zip(cls, v):
|
|
if not v.isdigit() or len(v) != 5:
|
|
raise ValueError("ZIP code must be 5 digits")
|
|
return v
|
|
|
|
class Person(BaseModel):
|
|
name: str
|
|
addresses: List[Address] # Nested structure with validation
|
|
```
|
|
|
|
For more on nested structures, see the [Nested Structure](nested_structure.md) guide.
|
|
|
|
## List Item Validation
|
|
|
|
You can validate items in a list:
|
|
|
|
```python
|
|
from typing import List
|
|
from pydantic import BaseModel, Field, field_validator
|
|
import instructor
|
|
from openai import OpenAI
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class TagList(BaseModel):
|
|
tags: List[str] = Field(..., min_items=1, max_items=5)
|
|
|
|
@field_validator('tags')
|
|
@classmethod
|
|
def validate_tags(cls, tags):
|
|
# Convert all tags to lowercase
|
|
tags = [tag.lower() for tag in tags]
|
|
|
|
# Check for minimum length of each tag
|
|
for tag in tags:
|
|
if len(tag) < 2:
|
|
raise ValueError("Each tag must be at least 2 characters")
|
|
|
|
# Check for duplicates
|
|
if len(tags) != len(set(tags)):
|
|
raise ValueError("Tags must be unique")
|
|
|
|
return tags
|
|
```
|
|
|
|
For more on lists, see the [List Extraction](list_extraction.md) guide.
|
|
|
|
## Using Enumerations for Validation
|
|
|
|
Enums provide a way to validate fields against a predefined set of values:
|
|
|
|
```python
|
|
from enum import Enum
|
|
from pydantic import BaseModel
|
|
import instructor
|
|
from openai import OpenAI
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class Status(str, Enum):
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
|
|
class Priority(str, Enum):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
|
|
class Task(BaseModel):
|
|
title: str
|
|
description: str
|
|
status: Status # Must be one of the enum values
|
|
priority: Priority # Must be one of the enum values
|
|
|
|
# Extract with enum validation
|
|
response = client.create(
|
|
model="gpt-5.4-mini",
|
|
messages=[
|
|
{"role": "user", "content": "Task: Update website, Description: Refresh content on homepage, Status: pending, Priority: high"}
|
|
],
|
|
response_model=Task
|
|
)
|
|
```
|
|
|
|
For more information on enums, see the [Enums](../../concepts/enums.md) concepts page.
|
|
|
|
## Custom Error Messages
|
|
|
|
You can customize validation error messages for better feedback:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
import instructor
|
|
from openai import OpenAI
|
|
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class CreditCard(BaseModel):
|
|
number: str = Field(
|
|
...,
|
|
pattern=r'^\d{16}$',
|
|
json_schema_extra={"error_msg": "Credit card number must be exactly 16 digits"}
|
|
)
|
|
expiry_month: int = Field(
|
|
...,
|
|
ge=1,
|
|
le=12,
|
|
json_schema_extra={"error_msg": "Expiry month must be between 1 and 12"}
|
|
)
|
|
expiry_year: int = Field(
|
|
...,
|
|
ge=2023,
|
|
le=2030,
|
|
json_schema_extra={"error_msg": "Expiry year must be between 2023 and 2030"}
|
|
)
|
|
cvv: str = Field(
|
|
...,
|
|
pattern=r'^\d{3,4}$',
|
|
json_schema_extra={"error_msg": "CVV must be 3 or 4 digits"}
|
|
)
|
|
```
|
|
|
|
## Handling Validation Failures
|
|
|
|
When validation fails, Instructor will:
|
|
|
|
1. Capture the validation error
|
|
2. Add the error message to the context
|
|
3. Retry the request with this feedback (if retries are enabled)
|
|
|
|
To control retry behavior:
|
|
|
|
```python
|
|
client = instructor.from_provider(
|
|
"openai/gpt-4o",
|
|
max_retries=2, # Number of retries after the initial attempt
|
|
throw_error=True # Whether to raise an exception on validation failure
|
|
)
|
|
```
|
|
|
|
For more on retries, see the [Retry Mechanisms](../validation/retry_mechanisms.md) guide.
|
|
|
|
## Real-world Example: Form Data Validation
|
|
|
|
Here's a more complete example validating form inputs:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
import instructor
|
|
import re
|
|
from datetime import date, datetime
|
|
from typing import Optional
|
|
client = instructor.from_provider("openai/gpt-5-nano")
|
|
|
|
class RegistrationForm(BaseModel):
|
|
username: str = Field(..., min_length=3, max_length=20)
|
|
email: str
|
|
password: str
|
|
confirm_password: str
|
|
birth_date: date
|
|
|
|
@field_validator('username')
|
|
@classmethod
|
|
def validate_username(cls, v):
|
|
if not re.match(r'^[a-zA-Z0-9_]+$', v):
|
|
raise ValueError("Username can only contain letters, numbers, and underscores")
|
|
return v
|
|
|
|
@field_validator('email')
|
|
@classmethod
|
|
def validate_email(cls, v):
|
|
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', v):
|
|
raise ValueError("Invalid email format")
|
|
return v
|
|
|
|
@field_validator('password')
|
|
@classmethod
|
|
def validate_password(cls, v):
|
|
if len(v) < 8:
|
|
raise ValueError("Password must be at least 8 characters")
|
|
if not re.search(r'[A-Z]', v):
|
|
raise ValueError("Password must contain at least one uppercase letter")
|
|
if not re.search(r'[a-z]', v):
|
|
raise ValueError("Password must contain at least one lowercase letter")
|
|
if not re.search(r'[0-9]', v):
|
|
raise ValueError("Password must contain at least one number")
|
|
return v
|
|
|
|
@field_validator('birth_date')
|
|
@classmethod
|
|
def validate_age(cls, v):
|
|
today = date.today()
|
|
age = today.year - v.year - ((today.month, today.day) < (v.month, v.day))
|
|
if age < 18:
|
|
raise ValueError("You must be at least 18 years old to register")
|
|
return v
|
|
|
|
@model_validator(mode='after')
|
|
def passwords_match(self):
|
|
if self.password != self.confirm_password:
|
|
raise ValueError("Passwords do not match")
|
|
return self
|
|
```
|
|
|
|
## Related Resources
|
|
|
|
- [Validation Basics](../validation/basics.md) - Core validation concepts
|
|
- [Custom Validators](../validation/custom_validators.md) - Creating custom validation logic
|
|
- [Field-level Validation](../validation/field_level_validation.md) - Advanced field validation
|
|
- [Retry Mechanisms](../validation/retry_mechanisms.md) - Handling validation failures
|
|
- [Fields](../../concepts/fields.md) - Understanding field definitions
|
|
- [Enums](../../concepts/enums.md) - Using enumeration types
|
|
|
|
## Next Steps
|
|
|
|
- Learn about [Optional Fields](optional_fields.md) for handling missing data
|
|
- Explore [Custom Validators](../validation/custom_validators.md) for complex validation
|
|
- Check out [Nested Structure](nested_structure.md) for complex data relationships |