참고소스 수정본
This commit is contained in:
385
참고/instructor-main/docs/learning/patterns/field_validation.md
Normal file
385
참고/instructor-main/docs/learning/patterns/field_validation.md
Normal file
@@ -0,0 +1,385 @@
|
||||
# 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-3.5-turbo",
|
||||
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-3.5-turbo",
|
||||
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-3.5-turbo",
|
||||
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
|
||||
Reference in New Issue
Block a user