High severity intermediate · Fix: 5-15 min

ValidationError

pydantic.ValidationError (raised by guardrails.Guard.from_pydantic)

What this error means
Guardrails Guard.from_pydantic raises ValidationError when the LLM-generated JSON doesn't match the Pydantic model's schema constraints, field types, or required fields.

Stack trace

traceback
pydantic_core._pydantic_core.ValidationError: 1 validation error for UserProfile
email
  value is not a valid email address (type=value_error.email)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "app.py", line 42, in <module>
    validated_output = guard.validate(llm_response)
  File "guardrails/guard.py", line 287, in validate
    return self._validate_with_pydantic(output)
  File "guardrails/guard.py", line 312, in _validate_with_pydantic
    return self.pydantic_model.model_validate(parsed_json)
pydantic.ValidationError: 1 validation error for UserProfile
email
  value is not a valid email address (type=value_error.email)
QUICK FIX
Add detailed type and format constraints to your Pydantic Field() definitions and mirror those constraints verbatim in your LLM prompt (email format, age range, required fields, string length limits).

Why it happens

Guardrails Guard.from_pydantic creates a validation schema from your Pydantic model and enforces all field constraints (type, format, required status, min/max values, regex patterns) on the LLM output. When the LLM returns JSON that parses syntactically but violates these constraints: such as an invalid email format, a string instead of an integer, a missing required field, or a value outside allowed bounds: Pydantic's validator raises ValidationError. This is the correct behavior: it's guardrails catching schema violations before they reach your application logic.

Detection

Enable debug logging on the Guard instance and capture the raw LLM response before validation. Log both the parsed JSON and the ValidationError details to identify which field(s) failed and why. Wrap guard.validate() calls in try/except and print the exception's error_count() and errors() methods to see exactly which constraints were violated.

Causes & fixes

1

LLM returned an invalid value for a typed field (e.g., string instead of integer, or non-email string in an EmailStr field)

✓ Fix

Add explicit type and format constraints to your prompt: 'The age field MUST be a number between 18 and 120. The email field MUST be a valid email address (format: user@domain.com).' Use Pydantic field validators or Field(description=...) to make expectations crystal clear in the prompt context.

2

LLM omitted a required field or returned null for a non-optional field in the Pydantic model

✓ Fix

Mark all truly required fields with no default in your Pydantic model, and explicitly instruct the LLM: 'ALWAYS include these fields: name, email, age. Do NOT omit any field. Do NOT return null.' Use Field(description='Required. Format: ...') for clarity.

3

LLM returned a value outside the allowed range (e.g., age=150, length > max_length constraint)

✓ Fix

Add explicit numeric and length constraints to your Pydantic model using Field(ge=18, le=120) for ranges and Field(max_length=50) for strings. Include these constraints in your prompt: 'Age must be between 18 and 120. Name must be 50 characters or less.'

4

Pydantic model has strict=True or model_config with validation_mode='strict', rejecting type coercion

✓ Fix

If type coercion is acceptable, remove strict=True or set model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) to auto-clean whitespace. Or instruct the LLM to return the exact types expected: 'age must be a JSON number, not a string.'

Code: broken vs fixed

Broken - triggers the error
python
from guardrails import Guard
from pydantic import BaseModel, EmailStr
import os
import json
from openai import OpenAI

class UserProfile(BaseModel):
    name: str
    email: EmailStr
    age: int

# BROKEN: No validation constraints in prompt, Pydantic rejects invalid email
guard = Guard.from_pydantic(UserProfile)
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))

prompt = "Extract user info from this text: 'John is 25 years old and his email is john-at-example-dot-com'"
response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': prompt}]
)

llm_output = response.choices[0].message.content
parsed_json = json.loads(llm_output)  # Might parse as {"name": "John", "email": "john-at-example-dot-com", "age": 25}
# This line fails: ValidationError because 'john-at-example-dot-com' is not a valid email
validated = guard.validate(parsed_json)
Fixed - works correctly
python
from guardrails import Guard
from pydantic import BaseModel, EmailStr, Field
import os
import json
from openai import OpenAI

class UserProfile(BaseModel):
    name: str = Field(description="User's full name", max_length=100)
    email: EmailStr = Field(description="User's email address in format: user@domain.com")
    age: int = Field(description="User's age between 18 and 120", ge=18, le=120)

# FIXED: Guardrails will validate against Pydantic schema with explicit constraints
guard = Guard.from_pydantic(UserProfile)
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))

# FIXED: Prompt now explicitly states all constraints matching Pydantic model
prompt = """Extract user info and return ONLY valid JSON (no markdown). Follow these rules:
- name: required, max 100 characters
- email: required, MUST be valid email format (user@domain.com, not 'user-at-domain')
- age: required, MUST be a number between 18 and 120

Text: 'John is 25 years old and his email is john@example.com'

Return JSON only: {"name": "...", "email": "...", "age": ...}"""

response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': prompt}]
)

llm_output = response.choices[0].message.content
parsed_json = json.loads(llm_output)

try:
    # FIXED: Now receives valid JSON with proper email format and age in range
    validated = guard.validate(parsed_json)
    print(f"✓ Validation passed: {validated}")
except Exception as e:
    print(f"✗ Validation failed: {e}")
    # Log raw LLM output and error for debugging
    print(f"LLM returned: {parsed_json}")
Added explicit Pydantic Field constraints (max_length, ge/le for age range, EmailStr validation) and updated the prompt to mirror those exact constraints, ensuring the LLM understands what valid output looks like before validation runs.

Workaround

Wrap guard.validate() in try/except, catch ValidationError, extract the errors using error.errors(), and implement a retry loop that sends the validation error details back to the LLM with corrected instructions. For example, if validation fails on email format, send a new prompt: 'Your previous email format was invalid. Please return: {"email": "john@example.com"} with a valid @ symbol.' This is fragile but buys time before refactoring the schema.

Prevention

Use Guardrails' structured output validators (OpenAI's response_format='json_object' or Anthropic's tool use) to guarantee schema-valid responses at the API level, bypassing client-side Pydantic validation entirely. Alternatively, use guardrails Hub validators like ValidJson and SummarizedJson which handle format validation before Pydantic runs. Test your Pydantic model against 10+ sample LLM outputs in your CI/CD pipeline to catch schema mismatches early.

Python 3.9+ · guardrails-ai >=0.5.0 · tested on 0.5.x
Verified 2026-04 · gpt-4o-mini
Verify ↗

Community Notes

No notes yetBe the first to share a version-specific fix or tip.