Skip to content

Validation

Agent-Airlock provides strict argument validation to prevent LLM-related bugs.

Ghost Argument Detection

"Ghost arguments" are parameters the LLM invents that don't exist in your function signature.

The Problem

def delete_user(user_id: int) -> dict:
    return {"deleted": user_id}

# LLM calls with invented parameters
delete_user(user_id=123, force=True, bypass_audit=True)

Without Airlock, force and bypass_audit are silently ignored by **kwargs or cause a TypeError.

The Solution

from agent_airlock import Airlock, AirlockConfig

# Permissive mode: strip ghost arguments
@Airlock(config=AirlockConfig(strict_mode=False))
def delete_user(user_id: int) -> dict:
    return {"deleted": user_id}

# Strict mode: reject ghost arguments
@Airlock(config=AirlockConfig(strict_mode=True))
def delete_user_strict(user_id: int) -> dict:
    return {"deleted": user_id}

Permissive Mode (Default)

Ghost arguments are stripped and logged:

result = delete_user(user_id=123, force=True)
# Logs: WARNING - Stripped ghost arguments: ['force']
# Returns: {"deleted": 123}

Strict Mode

Ghost arguments cause rejection:

result = delete_user_strict(user_id=123, force=True)
# Returns: AirlockResponse(
#     status="blocked",
#     error="Ghost arguments not allowed",
#     fix_hints=["Remove unknown parameters: force"],
#     blocked_args=["force"]
# )

Type Validation

Airlock uses Pydantic V2 strict mode - no type coercion allowed.

The Problem

Standard Pydantic allows coercion:

# Without strict mode
def get_user(user_id: int) -> dict: ...

get_user(user_id="123")  # Works! "123" coerced to 123
get_user(user_id="abc")  # Crashes at runtime

The Solution

Airlock enforces exact types:

from agent_airlock import Airlock

@Airlock()
def get_user(user_id: int) -> dict:
    return {"id": user_id}

result = get_user(user_id="123")
# Returns: AirlockResponse(
#     status="blocked",
#     error="Type validation failed",
#     fix_hints=["user_id: Expected int, got str. Try: user_id=123"]
# )

Supported Types

Airlock validates all standard Python types:

Type Valid Invalid
int 123 "123", 123.0
float 3.14, 3 "3.14"
str "hello" 123, None
bool True, False 1, 0, "true"
list[int] [1, 2, 3] [1, "2", 3]
dict[str, int] {"a": 1} {"a": "1"}
Optional[int] 123, None "123"

Complex Types

Airlock supports Pydantic models:

from pydantic import BaseModel
from agent_airlock import Airlock

class UserCreate(BaseModel):
    name: str
    email: str
    age: int

@Airlock()
def create_user(user: UserCreate) -> dict:
    return {"created": user.name}

# Valid
result = create_user(user={"name": "John", "email": "j@x.com", "age": 30})

# Invalid - age is string
result = create_user(user={"name": "John", "email": "j@x.com", "age": "30"})
# Blocked with fix_hints

Self-Healing Responses

When validation fails, Airlock returns actionable hints:

@Airlock()
def search(query: str, limit: int = 10, offset: int = 0) -> list:
    return []

result = search(query=123, limit="ten", extra_param=True)
# Returns:
# {
#     "status": "blocked",
#     "error": "Validation failed",
#     "fix_hints": [
#         "query: Expected str, got int. Try: query='123'",
#         "limit: Expected int, got str. Try: limit=10",
#         "Remove unknown parameters: extra_param"
#     ]
# }

The LLM can read these hints and retry with corrected arguments.

Validation Hooks

Monitor validation errors:

from agent_airlock import Airlock, AirlockConfig
from pydantic import ValidationError

def on_validation_error(tool_name: str, error: ValidationError):
    # Log to monitoring system
    print(f"Validation error in {tool_name}: {error.error_count()} issues")

config = AirlockConfig(on_validation_error=on_validation_error)

@Airlock(config=config)
def my_tool(x: int) -> int:
    return x

Framework Compatibility

Airlock preserves function signatures for framework compatibility:

import inspect
from agent_airlock import Airlock

@Airlock()
def my_tool(query: str, limit: int = 10) -> list:
    return []

# Signature is preserved
sig = inspect.signature(my_tool)
assert "query" in sig.parameters
assert sig.parameters["limit"].default == 10

This ensures compatibility with: - OpenAI Function Calling - Azure OpenAI - LangChain - FastMCP - Any framework that inspects function signatures