Skip to content
AI-grafen
EUniversityAgents and tool use· about 60 min· evolving, reviewed regularly· verified 2026-09-20· EN

Tool schemas, validation and error handling

Be able to define tools with a JSON schema and handle invalid calls robustly.

Prerequisites

Intuition

A tool schema is a contract between the model and your code. The stricter the contract, the fewer ways there are of breaking it.

Four things that make a schema good:

  1. enum instead of a free string where the set of values is known — then the model cannot invent categories.
  2. Bounds on numbers and lengths (minimum, maxLength) — they catch unreasonable calls before they are run.
  3. required and additionalProperties: false — no missing or invented fields.
  4. Descriptions the model reads — description is not documentation for humans, it is part of the prompt.

The model sees only the schema. A field called q with no description will be used wrongly.

Code

from typing import Literal
from pydantic import BaseModel, Field, ValidationError
import json

class SearchCases(BaseModel):
    """Search cases in the support system. Returns at most 50 hits."""
    status: Literal["open", "closed", "waiting"] = Field(description="The case's current status")
    older_than_days: int | None = Field(default=None, ge=0, le=365,
                                        description="Only cases older than this number of days")
    free_text: str | None = Field(default=None, max_length=200,
                                  description="A search term in the case's title or description")
    model_config = {"extra": "forbid"}          # additionalProperties: false

TOOLS = {"search_cases": (SearchCases, search_cases_impl)}

def spec():
    return [{"type": "function", "function": {
                "name": name,
                "description": (model.__doc__ or "").strip(),
                "parameters": model.model_json_schema()}}
            for name, (model, _) in TOOLS.items()]

def run(name: str, arguments: str) -> dict:
    if name not in TOOLS:
        return {"error": f"unknown tool '{name}'. Available: {sorted(TOOLS)}"}
    schema, fn = TOOLS[name]
    try:
        args = schema.model_validate_json(arguments)
    except ValidationError as e:
        errors = [{"field": ".".join(str(x) for x in f["loc"]), "problem": f["msg"]} for f in e.errors()]
        return {"error": "invalid arguments", "details": errors}    # structured → the model can correct itself
    except json.JSONDecodeError as e:
        return {"error": f"the arguments are not valid JSON: {e}"}
    try:
        return fn(args)
    except Exception as e:
        return {"error": f"the tool failed: {type(e).__name__}"}    # never leak a stack trace

The error message is part of the interface. «invalid arguments» helps nobody; {"field": "older_than_days", "problem": "Input should be less than or equal to 365"} makes the model correct itself in the next round. Send the error back as the tool result instead of aborting.

Mastery means

  • Defines tools with a JSON schema
  • Validates the arguments and handles invalid calls
  • Designs error messages the model can correct itself from

Sign in to do the exercises and build your mastery up.

Sources

All the sources and licences