Skip to content
Blog

Validating Tool Outputs with Pydantic Contracts

Learn how to enforce rigorous Pydantic schema validation on external tool outputs in AI agent pipelines, preventing downstream corruption and silent failures.

Published on September 11, 2026

AI Assistant

Autonomous AI agents rely heavily on tools—calling web APIs, database connectors, and internal microservices. However, raw outputs from external tools are inherently untrusted. APIs change response shapes, databases return None unexpectedly, and scraping tools yield malformed JSON. Without strict validation contracts, bad tool outputs propagate through agent memory and corrupt downstream reasoning.

In this guide, we explore how to construct strict Pydantic Contracts that validate and sanitize tool outputs before they ever reach an LLM’s context window.

The Problem: Garbage Tools, Corrupted Reasoning

When an agent invokes a tool, LLM runtimes typically deserialize the return value directly into the context string. Consider a weather API tool returning an unexpected payload structure:

# Unvalidated tool output passed straight to LLM
{"status": "error", "message": "Rate limit exceeded", "code": 429}

If the agent expected {"temperature_celsius": 22.5}, the LLM may attempt to parse "Rate limit exceeded" as degrees or hallucinate missing numeric fields. By wrapping tool execution in Pydantic models with explicit validation and parsing logic, we ensure that bad inputs are caught and handled deterministically before affecting agent state.

Defining the Pydantic Output Contract

Using Pydantic v2, we define strict schema boundaries with custom validators, field constraints, and field aliases.

from typing import Annotated
from pydantic import BaseModel, Field, HttpUrl, field_validator

class UserProfileContract(BaseModel):
    user_id: str = Field(..., min_length=3, max_length=64, description="Unique system user ID")
    email: str = Field(..., description="User primary email address")
    account_status: str = Field(..., pattern="^(active|suspended|pending)$")
    credit_score: Annotated[int, Field(ge=300, le=850)]
    profile_url: HttpUrl

    @field_validator("email")
    @classmethod
    def sanitize_email(cls, v: str) -> str:
        clean_email = v.strip().lower()
        if "@" not in clean_email:
            raise ValueError("Invalid email format")
        return clean_email

Wrapping Tool Execution in a Validated Pipeline

Instead of exposing raw functions to your agent runtime, encapsulate execution inside a contract-checking decorator:

from typing import Type, TypeVar, Callable, Any
from pydantic import ValidationError

T = TypeVar("T", bound=BaseModel)

def enforce_contract(schema: Type[T]):
    def decorator(func: Callable[..., Any]):
        def wrapper(*args, **kwargs) -> T:
            raw_output = func(*args, **kwargs)
            try:
                if isinstance(raw_output, dict):
                    return schema.model_validate(raw_output)
                elif isinstance(raw_output, str):
                    return schema.model_validate_json(raw_output)
                return schema.model_validate(raw_output)
            except ValidationError as e:
                # Log structured telemetry and return a standardized error object
                raise ValueError(f"Tool output violated contract schema: {e.errors()}")
        return wrapper
    return decorator

@enforce_contract(UserProfileContract)
def fetch_user_data(user_id: str) -> dict:
    # Simulating external database/API fetch
    return {
        "user_id": user_id,
        "email": " USER@EXAMPLE.COM ",
        "account_status": "active",
        "credit_score": 740,
        "profile_url": "https://example.com/profiles/user123"
    }

Key Benefits of Pydantic Tool Contracts

  1. Deterministic Type Conversion: Automatic coercion of numeric strings ("740" -> 740) and URL strings into verified objects.
  2. Clear Error Signals for LLMs: When a tool contract fails, the system returns a clean, structured validation error message to the LLM, prompting a retry with corrected arguments.
  3. Telemetry & Observability: Schema validation errors provide immediate signals in OpenTelemetry traces when upstream services break their contract.

For comprehensive documentation on Pydantic schemas, validation rules, and custom types, refer to the official Pydantic Documentation.