Beautiful, AI-friendly Pydantic error formatting with Rust-style diagnostics.
Your LLMs struggling to interpret Pydantic errors? Try Pydantic AI Errors!
error[PYD001]: invalid value for field `user.name`
--> input.json:3:13
|
1 | {
2 | "user": {
3 | "name": "",
| ^^ expected a string (min 2 chars), found string ""
4 | "age": "sixteen",
5 | "email": "not-an-email",
|
= help: provide a string with at least 2 characters
error[PYD002]: type mismatch for field `user.age`
--> input.json:4:12
|
1 | {
2 | "user": {
3 | "name": "",
4 | "age": "sixteen",
| ^^^^^^^^^ expected integer, found string "sixteen"
5 | "email": "not-an-email",
|
= help: convert the string "sixteen" to an integer
- Source location tracking — Parses JSON while tracking line/column positions for each value
- Rich diagnostics — Converts Pydantic validation errors to structured diagnostics with full context
- Rust-style output — Beautiful error formatting inspired by the Rust compiler:
- Error codes (
PYD001,PYD002, etc.) - File location pointers (
--> input.json:3:12) - Surrounding context lines with line numbers
- Underlined error locations with
^^^ - Actionable help suggestions (
= help: ...)
- Error codes (
- ANSI color support — Colorized output for terminal display (can be disabled)
- Compact mode — Show all errors in a single error window for a consolidated view
- Custom error messages — Override default error messages per field for user-friendly output
- AI-friendly — Structured output that's easy for LLMs to parse and act on
uv add pydantic-ai-errorspip install pydantic-ai-errorsRequires Pydantic v2.
from pydantic import BaseModel, Field
from pydantic_ai_errors import parse_json
class User(BaseModel):
name: str = Field(min_length=2)
age: int = Field(ge=18)
email: str
json_input = '''{
"name": "",
"age": "sixteen",
"email": "not-valid"
}'''
result = parse_json(json_input, User, filename="user.json") # filename=label (optional)
if not result.success:
print(result.formatted)
# Also available: result.diagnostics for programmatic accessShow all errors in a single consolidated error window:
result = parse_json(json_input, User, filename="user.json", compact=True)Output:
error[PYD001]: invalid value for field `name`
error[PYD002]: type mismatch for field `age`
error[PYD003]: invalid email for field `email`
--> user.json:2:11
|
1 | {
2 | "name": "",
| ^^ expected a string (min 2 chars), found string ""
3 | "age": "sixteen",
| ^^^^^^^^^ expected integer, found string "sixteen"
4 | "email": "not-valid"
|
= help: provide a string with at least 2 characters
= help: convert the string "sixteen" to an integer
Override default error messages for specific fields:
result = parse_json(
json_input,
User,
filename="user.json",
custom_messages={
("name",): "Username must be at least 2 characters",
("age",): "Please enter a valid age as a number",
("user", "email"): "Invalid email format", # for nested fields
},
)If you already have a ValidationError from a previous validation:
from pydantic import BaseModel, ValidationError
from pydantic_ai_errors import format_pydantic_error
class Config(BaseModel):
name: str
json_string = '{"name": 123}'
try:
Config.model_validate_json(json_string)
except ValidationError as e:
formatted = format_pydantic_error(e, json_string, filename="config.json") # filename=label (optional)
print(formatted)from pydantic import BaseModel, Field
from pydantic_ai_errors import create_validator
class Config(BaseModel):
port: int = Field(ge=1, le=65535)
host: str
validate_config = create_validator(Config, filename="config.json", colors=True) # filename=label (optional)
result = validate_config(json_string)
if not result.success:
print(result.formatted)
exit(1)
# result.data is typed as Config
print(f"Server running on {result.data.host}:{result.data.port}")Parse and validate JSON against a Pydantic model.
Parameters:
json_string: str— The JSON string to parse and validatemodel: type[BaseModel]— The Pydantic model to validate againstfilename: str = ""— Label to display in error locations (optional)colors: bool = True— Enable ANSI colorscontext_lines: int = 4— Number of context lines before/after errorthrow: bool = False— Raise exception instead of returning failurecompact: bool = False— Show all errors in a single error windowcustom_messages: dict[tuple, str] = None— Custom error messages per field path
Returns: ValidationSuccess[T] | ValidationFailure
Format an existing ValidationError with source context.
Parameters:
error: ValidationError— The Pydantic error to formatjson_string: str— The original JSON stringfilename: str = ""— Label to display (optional)colors: bool = True— Enable ANSI colorscontext_lines: int = 4— Number of context linescompact: bool = False— Show all errors in a single error windowcustom_messages: dict[tuple, str] = None— Custom error messages per field path
Returns: str
Create a reusable validator function.
Parameters:
model: type[BaseModel]— The Pydantic model to validate againstfilename: str = ""— Default label to display in error locationscolors: bool = True— Enable ANSI colorscontext_lines: int = 4— Number of context linescompact: bool = False— Show all errors in a single error windowcustom_messages: dict[tuple, str] = None— Custom error messages per field path
Returns: Callable[[str], ValidationResult[T]]
For programmatic access to error details:
@dataclass
class Diagnostic:
code: str # e.g., 'PYD001'
severity: DiagnosticSeverity
message: str # e.g., 'type mismatch for field `user.age`'
path: tuple[str | int, ...] # e.g., ('user', 'age')
span: SourceSpan | None # Source location info
help: str | None # Actionable suggestion
expected: str | None # Expected type/value
received: str | None # Actual type/valueRun the included example:
cd python
pip install -e ".[dev]"
python examples/01_basic.pyApache-2.0