Validation System

Format-agnostic validation for tool arguments and data schemas

Overview

Aegeantic provides a flexible validation system that supports multiple validation formats. The system is extensible, allowing you to use JSON Schema, custom validators, or the built-in simple validator.

Design Philosophy: The validation system is format-agnostic. You choose which validator to use based on your needs, and all validators implement the same interface.

Validator Registry

The ValidatorRegistry manages all available validators:

from agentic import ValidatorRegistry

# Create registry (simple and passthrough validators are pre-registered)
registry = ValidatorRegistry()

# Validate data
is_valid, errors = registry.validate(
    data={"name": "John", "age": 30},
    schema={
        "validator": "simple",
        "required": ["name", "age"],
        "fields": {
            "name": {"type": "str", "min_length": 1},
            "age": {"type": "int", "min": 0}
        }
    }
)

Simple Validator

Built-in validator for common validation scenarios:

Type Validation

schema = {
    "validator": "simple",
    "required": ["name"],
    "fields": {
        "name": {"type": "str"},
        "age": {"type": "int"},
        "score": {"type": "float"},
        "active": {"type": "bool"},
        "items": {"type": "list"},
        "metadata": {"type": "dict"}
    }
}

String Validation

Constraint Description Example
min_length Minimum string length {"type": "str", "min_length": 5}
max_length Maximum string length {"type": "str", "max_length": 100}
pattern Regex pattern match {"type": "str", "pattern": "^[a-z]+$"}
schema = {
    "validator": "simple",
    "fields": {
        "username": {
            "type": "str",
            "min_length": 3,
            "max_length": 20,
            "pattern": "^[a-zA-Z0-9_]+$"
        },
        "email": {
            "type": "str",
            "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
        }
    }
}

Numeric Validation

Constraint Description Example
min Minimum value (inclusive) {"type": "int", "min": 0}
max Maximum value (inclusive) {"type": "int", "max": 100}
schema = {
    "validator": "simple",
    "fields": {
        "age": {
            "type": "int",
            "min": 0,
            "max": 150
        },
        "probability": {
            "type": "float",
            "min": 0.0,
            "max": 1.0
        },
        "temperature": {
            "type": "float",
            "min": 0.0,
            "max": 2.0
        }
    }
}

List Validation

Constraint Description Example
min_items Minimum list length {"type": "list", "min_items": 1}
max_items Maximum list length {"type": "list", "max_items": 10}
schema = {
    "validator": "simple",
    "fields": {
        "tags": {
            "type": "list",
            "min_items": 1,
            "max_items": 5
        }
    }
}

Custom Validator Functions

def is_valid_url(value):
    return isinstance(value, str) and value.startswith(("http://", "https://"))

def is_positive_even(value):
    return isinstance(value, int) and value > 0 and value % 2 == 0

schema = {
    "validator": "simple",
    "fields": {
        "url": {
            "type": "str",
            "validator_func": is_valid_url
        },
        "count": {
            "type": "int",
            "validator_func": is_positive_even
        }
    }
}

Passthrough Validator

Validator that skips all validation checks:

schema = {
    "validator": "passthrough"
}

# Always returns (True, []) - useful for disabling validation on specific tools
is_valid, errors = registry.validate(any_data, schema)
assert is_valid == True
assert errors == []

Custom Validators

Create your own validator functions to extend the system:

from agentic import ValidationError

def custom_validator(value: dict, schema: dict) -> tuple[bool, list[ValidationError]]:
    """Custom validator function."""
    errors = []

    # Custom validation logic
    if "required_field" not in value:
        errors.append(ValidationError(
            field="required_field",
            message="This field is required",
            value=None
        ))

    if "custom_check" in value:
        if not _check_custom_rule(value["custom_check"]):
            errors.append(ValidationError(
                field="custom_check",
                message="Custom rule failed",
                value=value["custom_check"]
            ))

    is_valid = len(errors) == 0
    return is_valid, errors

def _check_custom_rule(value):
    # Your custom validation logic
    return True

# Register custom validator
registry.register("custom", custom_validator)

Tool Validation

Tools automatically validate arguments using the configured validator:

from agentic import create_tool, ToolRegistry, ValidatorRegistry

# Setup validator registry (simple and passthrough validators pre-registered)
validator_registry = ValidatorRegistry()

# Create tool registry with validators
tools = ToolRegistry(validator_registry=validator_registry)

# Create tool with validation
def search_web(args):
    query = args["query"]
    return {"results": [...]}

search_tool = create_tool(
    name="search",
    func=search_web,
    input_schema={
        "validator": "simple",
        "required": ["query"],
        "fields": {
            "query": {
                "type": "str",
                "min_length": 1,
                "max_length": 500
            },
            "max_results": {
                "type": "int",
                "min": 1,
                "max": 100
            }
        }
    }
)

tools.register(search_tool)

# Validation happens automatically during tool execution
# Invalid arguments will return ToolResult with success=False

Validation Errors

Validation errors provide detailed information about failures:

class ValidationError:
    field: str       # Field that failed validation
    message: str     # Error message
    value: Any       # The invalid value

# Example usage
is_valid, errors = registry.validate(data, schema)

if not is_valid:
    for error in errors:
        print(f"Field '{error.field}': {error.message}")
        print(f"Invalid value: {error.value}")

Complex Validation Examples

API Request Validation

api_request_schema = {
    "validator": "simple",
    "required": ["method", "url"],
    "fields": {
        "method": {
            "type": "str",
            "pattern": "^(GET|POST|PUT|DELETE|PATCH)$"
        },
        "url": {
            "type": "str",
            "pattern": "^https?://",
            "max_length": 2048
        },
        "headers": {
            "type": "dict"
        },
        "body": {
            "type": "dict"
        },
        "timeout": {
            "type": "int",
            "min": 1,
            "max": 300
        }
    }
}

Database Query Validation

query_schema = {
    "validator": "simple",
    "required": ["table", "operation"],
    "fields": {
        "table": {
            "type": "str",
            "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"  # Valid SQL identifier
        },
        "operation": {
            "type": "str",
            "pattern": "^(SELECT|INSERT|UPDATE|DELETE)$"
        },
        "where": {
            "type": "dict"
        },
        "limit": {
            "type": "int",
            "min": 1,
            "max": 1000
        }
    }
}

File Operation Validation

import os

def is_safe_path(path):
    # Prevent path traversal attacks
    return ".." not in path and not os.path.isabs(path)

file_op_schema = {
    "validator": "simple",
    "required": ["operation", "path"],
    "fields": {
        "operation": {
            "type": "str",
            "pattern": "^(read|write|delete)$"
        },
        "path": {
            "type": "str",
            "validator_func": is_safe_path,
            "max_length": 255
        },
        "content": {
            "type": "str",
            "max_length": 1_000_000  # 1MB limit
        }
    }
}

Disabling Validation

You can disable validation for specific agents or tools:

# Disable validation in agent config
config = AgentConfig(
    agent_id="my_agent",
    validate_tool_arguments=False  # Skip validation
)

# Or create tools without schemas
tool = create_tool(
    name="unvalidated_tool",
    func=my_function,
    input_schema={}  # No validation
)
Security: Always validate inputs from untrusted sources. Validation prevents injection attacks, invalid data, and unexpected behavior.

Best Practices

Performance Considerations

Validation adds minimal overhead:

For high-throughput scenarios:

Next Steps