Files
2026-05-12 19:40:31 +09:00

114 lines
3.5 KiB
Python

import json
from typing import Any, Dict, List, Optional
from jsonschema import Draft202012Validator, ValidationError
from referencing import Registry, jsonschema as jsonschema_ref
from guardrails.actions.reask import SkeletonReAsk
from guardrails_ai.types import FailResult
class SchemaValidationError(Exception):
fields: Dict[str, List[str]] = {}
def __init__(self, *args: object, fields: Dict[str, List[str]]):
self.fields = fields
super().__init__(*args)
def validate_against_schema(
payload: Any,
validator: Draft202012Validator,
*,
validate_subschema: Optional[bool] = False,
):
fields: Dict[str, List[str]] = {}
error: ValidationError
for error in validator.iter_errors(payload):
if validate_subschema is True and error.message.endswith(
"is a required property"
):
continue
fields[error.json_path] = fields.get(error.json_path, [])
fields[error.json_path].append(error.message)
if fields:
error_message = (
"The provided payload is not compliant with the provided schema!"
)
raise SchemaValidationError(error_message, fields=fields)
def validate_json_schema(json_schema: Dict[str, Any]):
"""Validates a json_schema, against the JSON Meta Schema Draft 2020-12.
Raises a SchemaValidationError if invalid.
"""
json_schema_validator = Draft202012Validator(
{
"$ref": "https://json-schema.org/draft/2020-12/schema",
}
)
try:
validate_against_schema(json_schema, json_schema_validator)
except SchemaValidationError as e:
schema_name = json_schema.get("title", json_schema.get("$id"))
error_message = (
f"Schema {schema_name} is not compliant with JSON Schema Draft 2020-12!"
)
raise SchemaValidationError(error_message, fields=e.fields)
def validate_payload(
payload: Any,
json_schema: Dict[str, Any],
*,
validate_subschema: Optional[bool] = False,
):
"""Validates a payload, against the provided JSON Schema.
Raises a SchemaValidationError if invalid.
"""
schema_id = json_schema.get("$id", "temp-schema")
registry = Registry().with_resources(
[
(
f"urn:{schema_id}",
jsonschema_ref.DRAFT202012.create_resource(json_schema),
)
]
)
validator = Draft202012Validator(
{
"$ref": f"urn:{schema_id}",
},
registry=registry,
# TODO: Add custom checks for date: format,
# time: format, date-time: format, etc.
# format_checker=draft202012_format_checker
)
validate_against_schema(payload, validator, validate_subschema=validate_subschema)
def schema_validation(llm_output: Any, output_schema: Dict[str, Any], **kwargs):
validate_subschema = kwargs.get("validate_subschema", False)
schema_error = None
try:
validate_payload(
llm_output, output_schema, validate_subschema=validate_subschema
)
except SchemaValidationError as sve:
formatted_error_fields = json.dumps(sve.fields, indent=2)
schema_error = f"JSON does not match schema:\n{formatted_error_fields}"
if schema_error:
return SkeletonReAsk(
incorrectValue=llm_output,
failResults=[
FailResult(
fixValue=None,
errorMessage=schema_error,
)
],
)